Compare commits

...

1085 Commits
28.7 ... master

Author SHA1 Message Date
ejurgensen
49171dac1a [artwork] Rename to artwork_get_by_file_id and artwork_get_by_group_id
For consistency with new artwork_get_by_queue_item_id
2025-10-27 22:54:11 +01:00
ejurgensen
09b9b0c7fc [airplay] Support artwork for non-library items, e.g. from Spotify search 2025-10-27 22:54:11 +01:00
ejurgensen
6f45f8b4a5 [dacp] Add support for non-library artwork, e.g. from Spotify searches
Fixes #1936
2025-10-27 22:54:11 +01:00
ejurgensen
921d4446d6 [artwork] Add function to get artwork for non-library queue items
Adds artwork_get_by_queue_item_id() and a source handler for getting artwork
from queue_item->artwork_url.
2025-10-27 22:54:11 +01:00
ejurgensen
bf598153f3
[docs] Document HDMI volume control changes for Raspberry Pi
Added information about HDMI volume control issues on Raspberry Pi.
2025-10-24 14:21:16 +02:00
github-actions[bot]
c1bdac931e [web] Rebuild web interface 2025-10-22 17:56:26 +00:00
Alain Nussbaumer
a1e4982c0b [web] Update to newer versions of libraries 2025-10-22 19:55:32 +02:00
Alain Nussbaumer
352a73044e Merge branch 'master' of github.com:owntone/owntone-server 2025-10-22 19:53:21 +02:00
ejurgensen
5f526c7a7e [dacp] Fix segfault from invalid queries
When parsing a DACP request (pattern: ^/ctrl-int/[[:digit:]]+/playqueue-edit$)
with a command parameter being "move" and an edit-params parameter lacking a
colon, strchr(param, ':') at httpd_dacp.c:2038 will return NULL, and safe_atoi32
is called with its first parameter str being 1. This will bypass the NULL check
at src/misc.c:650 and causes a segmentation fault at the call to strtol at line
657.

Closes #1933
2025-10-08 19:49:01 +02:00
ejurgensen
b7e385ffe0 [httpd] Better logging of evbuffer_read() errors
Fixes #1931
2025-09-30 20:44:01 +02:00
Alain Nussbaumer
2eba24b4ba [web] Update libraries 2025-09-24 10:43:15 +10:00
github-actions[bot]
336200727d [web] Rebuild web interface 2025-09-18 05:55:20 +00:00
Alain Nussbaumer
b523ea4d35 [web] Update to newer versions of libraries for security reasons 2025-09-18 15:54:38 +10:00
ejurgensen
bd5746c83e Bump to version 29.0 2025-09-12 23:11:01 +02:00
ejurgensen
b58307cc37 ChangeLog for OwnTone 29.0 2025-09-12 23:09:18 +02:00
github-actions[bot]
387660d96b [web] Rebuild web interface 2025-09-10 23:40:43 +00:00
Alain Nussbaumer
15fd59b2a1 [web] Fix unresponsive Spotify music page #1917 2025-09-11 09:40:01 +10:00
ejurgensen
82c8374cad [docs] Reinstalling Remote can sometimes solve pairing issues
Closes #1924
2025-08-31 19:51:54 +02:00
github-actions[bot]
1bdf4680ff [web] Rebuild web interface 2025-08-26 10:18:28 +00:00
Alain Nussbaumer
b1d7e4c433
Merge pull request #1921 from chme/fix/web-modal-mobile
[web] Fix modal hight to display buttons on mobile
2025-08-26 20:17:59 +10:00
github-actions[bot]
dc0eb24e7f [web] Rebuild web interface 2025-08-26 09:56:40 +00:00
Alain Nussbaumer
2dd693c0f2 Merge branch 'master' of github.com:owntone/owntone-server 2025-08-26 19:56:07 +10:00
Alain Nussbaumer
bb64df57ff [web] Show menu icons on mobile 2025-08-26 19:55:54 +10:00
Alain Nussbaumer
3f0041100a [web] Format source code 2025-08-26 19:55:21 +10:00
Alain Nussbaumer
9a721e49ab [web] Bump to newer versions of libraries 2025-08-26 19:53:31 +10:00
ejurgensen
ec632e478c [xcode] Fix deprecations of ->pix_fmts and av_format_inject_global_side_data 2025-08-25 22:50:21 +02:00
ejurgensen
19012bf616 [misc] Fix deprecation warning about referencing xmlBuffer->content 2025-08-25 22:50:21 +02:00
ejurgensen
d051d787ba [scan] Use plist_get_unix_date_val() if available
plist_get_date_val is deprecated
2025-08-25 22:50:21 +02:00
Drew Thompson
d8485bf3c2
[scan] Add support for "empty" operand in smart playlists
"empty" will be parsed to SQL NULL
2025-08-25 19:14:31 +02:00
ejurgensen
753a027ce1
Merge pull request #1920 from owntone/connect_timeout
[misc] Prevent OwnTone from hanging when a connection can't be made
2025-08-23 17:23:15 +02:00
ejurgensen
a84f4e09a0 [misc] Prevent OwnTone from hanging when a connection can't be made
This change adds a default 3s timeout to net_connect().

Fixes #1916
2025-08-23 17:22:18 +02:00
Christian Meffert
25d2af9343 [web] Fix modal hight to display buttons on mobile 2025-08-23 11:01:39 +02:00
ejurgensen
f85a800644
Merge pull request #1918 from owntone/libmount1
[scan] Use libmount to detect filesystem mount events
2025-08-22 20:39:10 +02:00
ejurgensen
ba8212b175 [scan] Use libmount to detect filesystem mount events
Resolves issue #1897
2025-08-22 19:57:53 +02:00
ejurgensen
d16343bddc [scan] Revert libmount commits due to MacOS/FreeBSD build errors
Reverts 3be8e4f and e607019
2025-08-21 23:50:24 +02:00
ejurgensen
3be8e4f479 [scan] Fix wrong symbol name for non-libmount builds 2025-08-21 23:39:55 +02:00
ejurgensen
e607019a1c [scan] Use libmount to detect filesystem mount events
Resolves issue #1897
2025-08-21 23:31:10 +02:00
ejurgensen
324b6eb61a [spotify] Update librespot-c to 0.7
Uses an extended metadata request like go-librespot to get file id, which
should mean that Spotify works even when not in legacy mode (tcp mode).

Ref issue #1912
2025-08-21 23:10:23 +02:00
github-actions[bot]
84d1b091ff [web] Rebuild web interface 2025-08-20 01:07:44 +00:00
Alain Nussbaumer
65b9323488 [web] Add page to control outputs #1450 2025-08-20 11:07:02 +10:00
github-actions[bot]
2ddaba6e77 [web] Rebuild web interface 2025-08-19 23:52:52 +00:00
Alain Nussbaumer
5b013115ba [web] Avoid list being modified when not in editing mode #1915 2025-08-20 09:50:29 +10:00
github-actions[bot]
efbf950068 [web] Rebuild web interface 2025-08-19 07:31:56 +00:00
Alain Nussbaumer
b9d821b46a [web] Add followed artists to the Spotify music page #1538 2025-08-19 17:31:16 +10:00
github-actions[bot]
411e028f9f [web] Rebuild web interface 2025-08-18 10:36:56 +00:00
Alain Nussbaumer
569e48ba7d [web] Add the cover artwork of Spotify playlists in the modal dialog 2025-08-18 20:36:11 +10:00
github-actions[bot]
31ff67797b [web] Rebuild web interface 2025-08-18 05:50:22 +00:00
Alain Nussbaumer
3bf17d8b6d [web] Remove unused variable and incorrect text 2025-08-18 15:49:22 +10:00
Alain Nussbaumer
02279236f3 [web] Ensure overlay covers entire screen when scrolling 2025-08-18 15:01:55 +10:00
github-actions[bot]
13f4c087e8 [web] Rebuild web interface 2025-08-18 00:42:24 +00:00
Alain Nussbaumer
ea6388b51e [web] Remove quality and position properties when no values are available 2025-08-18 10:41:45 +10:00
github-actions[bot]
58593809f9 [web] Rebuild web interface 2025-08-17 10:36:46 +00:00
Alain Nussbaumer
f7c4659899 [web] Display artist visuals from Spotify 2025-08-17 20:36:06 +10:00
Alain Nussbaumer
2d5dd3d7fd [web] Show cover of Spotify playlists 2025-08-17 19:34:17 +10:00
Alain Nussbaumer
978a9b6a96 [web] Switch to Spotify Web SDK 2025-08-17 12:28:32 +10:00
ejurgensen
b612e12aca [spotify] Fix misplaced librespot-c legacy mode selector
OwnTone was supposed to default to the old raw tcp Spotify protocol, which
would have left it unaffected by Spotify breaking the http response to the
metadata request. However, the mechanism for defaulting was misplaced. This
should put it in the right place and fix #1912.

Of course, the new protocol is still broken, but hopefully that will be fixed
on Spotify's side.
2025-08-09 21:55:37 +02:00
ejurgensen
75c9db5f59 [jsonapi] Add client_id and client_secret to /api/spotify
Closes #1909
2025-08-03 20:54:10 +02:00
ejurgensen
7b91d43274 [scan] Fix for platforms without fcopyfile() or copy_file_range()
Add @cagney's fallback copy function. Use feature tests in configure.ac for
fcopyfile() and copy_file_range().

Fixes #1901
2025-07-27 22:40:33 +02:00
github-actions[bot]
6a21cad6fd [web] Rebuild web interface 2025-07-27 01:00:32 +00:00
Alain Nussbaumer
f8a9b92504 [web] Update library versions 2025-07-27 10:59:42 +10:00
ejurgensen
5e4d40ee03
[dacp] Fix null dereference on missing mode param
Fixes #1907
2025-07-24 18:08:10 +02:00
ejurgensen
6d604a176a
Merge pull request #1902 from sigmaris/patch-1
Update dns-sd arguments for modern Apple Music app
2025-07-24 12:25:24 +02:00
Hugh Cole-Baker
34eedf4d1b
Update dns-sd arguments for modern Apple Music app
Just having the "ffid=..." argument doesn't seem to be enough to make the owntone library appear in recent versions of Apple's Music.app; I copied the extra arguments from a real instance of Music.app and with them, the owntone library is usable in Music.app like it used to work in iTunes.
2025-07-13 10:31:13 +01:00
github-actions[bot]
0017c9cace [web] Rebuild web interface 2025-06-21 11:06:22 +00:00
Alain Nussbaumer
d53856ee63 Merge branch 'master' of github.com:owntone/owntone-server 2025-06-21 13:05:46 +02:00
Alain Nussbaumer
5200c8289f [web] Update of library versions 2025-06-21 13:05:32 +02:00
ejurgensen
c09026f7c3 [spotify] Fix Bullseye breakage by updating librespot-c to 0.6 2025-06-21 00:21:49 +02:00
Alain Nussbaumer
6028c39408 [web] Fix player buttons not being highlighted 2025-06-09 15:22:33 +02:00
Alain Nussbaumer
ce59d36a96 [web] Streamline the modal dialogs 2025-06-09 14:07:38 +02:00
Alain Nussbaumer
0cfd753770 [web] Fix the display of channels in track modal dialogs 2025-06-09 09:57:39 +02:00
Alain Nussbaumer
939dab6a48 [web] Simplify appearance switching 2025-06-09 09:13:14 +02:00
Alain Nussbaumer
482a5bdafc [web] Fix top navigation bar colour 2025-06-08 09:14:23 +02:00
Alain Nussbaumer
227db7d502 [web] Update versions of libraries 2025-06-07 21:59:03 +02:00
Alain Nussbaumer
03e54140d7 [web] Remove unused code 2025-06-07 21:58:02 +02:00
Alain Nussbaumer
78a1137510 [web] Fix missing search result presentation 2025-06-07 21:57:30 +02:00
Alain Nussbaumer
c1ffbca09b [web] Remove border around slider thumb 2025-06-07 12:03:49 +02:00
Alain Nussbaumer
051498861e [web] Remove linting rule 2025-06-06 21:57:02 +02:00
Alain Nussbaumer
ef683f5b02 Merge remote-tracking branch 'origin/master' into web-3.0 2025-06-06 21:29:49 +02:00
Alain Nussbaumer
5dc748baf5 [web] Avoid hiding the top navigation bar when the player menu is displayed 2025-06-06 21:29:23 +02:00
Alain Nussbaumer
4bd8736346 [web] Add the ability to change the appearance of the UI 2025-06-06 21:21:31 +02:00
ejurgensen
781110659a
Update bug report issue template
Add note about removing irrelevant logging and Spotify secrets
2025-06-06 14:49:23 +02:00
Alain Nussbaumer
a7d4501632 [web] Remove linting rules 2025-06-02 20:35:45 +02:00
Alain Nussbaumer
5018cc4544 [web] Remove lint rule 2025-06-02 20:06:17 +02:00
Alain Nussbaumer
99ef7b8dfc [web] Fix linting configuration 2025-06-01 19:19:31 +02:00
Alain Nussbaumer
e476032776 [web] Fix missing reference 2025-06-01 19:17:17 +02:00
Alain Nussbaumer
60b688a182 [web] Lint source code 2025-06-01 18:05:12 +02:00
Alain Nussbaumer
4872bfd7fd [web] Minor changes 2025-05-31 19:48:18 +02:00
Alain Nussbaumer
e80e58f0fd Merge remote-tracking branch 'origin/master' into web-3.0 2025-05-30 21:24:50 +02:00
Alain Nussbaumer
9c61ee5158 [web] Simplify the display of settings to make them autogenerated in the future 2025-05-30 21:23:57 +02:00
Alain Nussbaumer
4dc6754726 [web] Move locale logic into the settings store 2025-05-30 19:18:25 +02:00
Alain Nussbaumer
fc24c2279f [web] Fix for the Spotify search 2025-05-29 21:56:47 +02:00
Alain Nussbaumer
5f2785171c [web] Rename player page 2025-05-29 21:40:47 +02:00
Alain Nussbaumer
0c7e94b903 [web] Fix display of player page when an item is added to an empty queue 2025-05-29 15:21:28 +02:00
Alain Nussbaumer
4531eaa75f [web] Reorder code and remove unused code 2025-05-29 13:46:22 +02:00
Alain Nussbaumer
a0d2ddcdc2 [web] Always show items the burger menu 2025-05-29 13:29:11 +02:00
Alain Nussbaumer
36d8161a37 [web] Remove unexpected initialisation of the configuration store 2025-05-29 13:14:27 +02:00
Alain Nussbaumer
36736e03a2 [web] Streamline stores 2025-05-28 21:51:51 +02:00
Alain Nussbaumer
91bac1273b [web] Hide navigation items on mobile device screens 2025-05-28 00:00:50 +02:00
Alain Nussbaumer
4adb623c3f [web] Fix a bug in the Spotify search 2025-05-27 23:46:33 +02:00
Alain Nussbaumer
eb33a25ce7 Merge remote-tracking branch 'origin/master' into web-3.0 2025-05-25 10:42:51 +02:00
Alain Nussbaumer
f62c5c06a8 [web] Lint source code 2025-05-25 10:41:58 +02:00
Alain Nussbaumer
02625ff67a [web] Make unsynchronised lyrics scrollable on mobile devices 2025-05-25 10:13:36 +02:00
Alain Nussbaumer
b251a4e418 [web] Remove ternary statements 2025-05-25 10:12:59 +02:00
Alain Nussbaumer
78ffba97d8 [web] Add helper method in stores 2025-05-24 10:18:59 +02:00
Alain Nussbaumer
04c119a3fd [web] Rename pages for more coherence 2025-05-24 10:18:36 +02:00
Alain Nussbaumer
59050c1018 [web] Add helper methods in stores 2025-05-24 09:21:36 +02:00
ejurgensen
4154a20cda [spotify] Don't log "Unexpected JSON" when getting a null element
Instead log a debug message. Ref issue #1892.
2025-05-21 23:28:08 +02:00
ejurgensen
3ccd117812
Merge pull request #1894 from owntone/macos_gettext1
[gha] Fix MacOS workflows failing with undefined macro errors mk2
2025-05-19 23:57:28 +02:00
ejurgensen
3c7e5d404b [gha] Fix MacOS workflows failing with undefined macro errors mk2
The macros shipped with Homebrew's gettext are no longer installed to
$(brew --prefix)/share/aclocal, so aclocal can't find them, resulting
in autoreconf failing.

Fixes issue #1889
2025-05-19 23:43:02 +02:00
Alain Nussbaumer
af632f4304 [web] Simplify image control 2025-05-19 19:48:08 +02:00
Alain Nussbaumer
708370aab9 [web] Refactor search pages 2025-05-18 18:35:58 +02:00
Alain Nussbaumer
b5fe530f0d [web] Better handling of events 2025-05-17 13:39:59 +02:00
Alain Nussbaumer
8bc8a2c9d8 [web] Fix for services store 2025-05-17 11:03:47 +02:00
Alain Nussbaumer
3b336657d4 [web] Fix call to queue method 2025-05-17 09:21:03 +02:00
Alain Nussbaumer
c2460d2f5f [web] Put specific style in the component 2025-05-17 09:20:31 +02:00
ejurgensen
4b1617971e [gha] Fix MacOS workflows failing with undefined macro errors
Add "brew install gettext" to MacOS workflows ref issue #1889
2025-05-17 01:31:48 +02:00
ejurgensen
b88df4b4e8 [airplay] Fix client name showing as blank when pairing with MacOS
Also include the X-Apple-HKP header in pair-pin-start like iOS does, just in
case.

Ref issue #1885
2025-05-17 01:19:27 +02:00
Alain Nussbaumer
f91189d93b [web] Improve the initialisation of stores 2025-05-17 00:24:58 +02:00
ejurgensen
7193d0a243 [spotify] Fix GCC 15 compile errors about implicit declarations
Resolves issue #1888
2025-05-16 23:49:09 +02:00
Alain Nussbaumer
6b5f4ff4d7 [web] Authorise lyrics that don't have timecodes 2025-05-14 18:30:11 +02:00
Alain Nussbaumer
82fb9a11b6 [web] Simplify styling 2025-05-13 19:13:51 +02:00
Alain Nussbaumer
3eb55620db [web] Fix linting errors 2025-05-13 18:46:33 +02:00
Alain Nussbaumer
f830949af1 [web] Streamline the name of components 2025-05-13 18:37:26 +02:00
Alain Nussbaumer
d010f8f655 [web] Rename lyrics component 2025-05-13 18:23:15 +02:00
Alain Nussbaumer
e2ae67c021 Merge remote-tracking branch 'origin/master' into web-3.0 2025-05-12 21:03:35 +02:00
Alain Nussbaumer
136732b024 [web] Fix last line of timed lyrics 2025-05-12 20:56:07 +02:00
Alain Nussbaumer
775eac28a5 [web] Improve CSS classes 2025-05-12 20:55:04 +02:00
Alain Nussbaumer
f3284e03ed [web] Fix library API calls errors 2025-05-12 20:54:25 +02:00
Alain Nussbaumer
468e9d8ba7 [web] Change the outputs API calls 2025-05-12 20:19:06 +02:00
Alain Nussbaumer
5c90442161 [web] Change the library API calls 2025-05-12 20:18:48 +02:00
Alain Nussbaumer
b4a73ff344 [web] Change queue API calls 2025-05-11 20:49:14 +02:00
Alain Nussbaumer
8048bb15ff [web] Change the remotes API calls 2025-05-11 20:44:50 +02:00
Alain Nussbaumer
c34c5eb585 [web] Change the settings API calls 2025-05-11 20:44:30 +02:00
Alain Nussbaumer
a229474da7 [web] Simplify player API calls 2025-05-11 20:31:08 +02:00
Alain Nussbaumer
ae59d23660 [web] Fix style of player controls 2025-05-11 20:14:33 +02:00
Alain Nussbaumer
dbfc727b21 [web] Change player controls to buttons 2025-05-11 09:12:29 +02:00
Alain Nussbaumer
b52fd89474 [web] Simplify audio library 2025-05-10 22:19:42 +02:00
github-actions[bot]
0c7d6d7770 [web] Rebuild web interface 2025-05-10 20:13:55 +00:00
Alain Nussbaumer
19399f3c08 [web] Fix unstoppable stream player 2025-05-10 22:13:04 +02:00
Alain Nussbaumer
a4086ee314 [web] Fix audio not stopping 2025-05-10 13:44:27 +02:00
Alain Nussbaumer
2c517ae8a6 [web] Improve lyrics pane component 2025-05-10 13:43:23 +02:00
Alain Nussbaumer
858e49bdb3 [web] Update library versions 2025-05-08 22:20:29 +02:00
Alain Nussbaumer
7548e4e059 [web] Fix security warnings 2025-05-08 22:15:10 +02:00
Alain Nussbaumer
30fdcd4427 [web] Change lyrics display 2025-05-08 22:00:59 +02:00
Alain Nussbaumer
5b29d43e5b [web] Remove useless computed property 2025-05-08 19:29:33 +02:00
ejurgensen
ae5be5f83e [spotify] OAuth redirect to https://owntone.github.io/owntone-oauth/spotify/
From Nov 2025 Spotify will require that the OAuth redirect is to a https url,
so we go via a Github hosted intermediate page.

Ref issue #1858
2025-05-04 22:59:56 +02:00
Alain Nussbaumer
b8a8899868 [web] Fix lyrics pan size 2025-05-04 19:41:04 +02:00
ejurgensen
216b6268c7 [docs] Android remotes no longer available from Google Play
See issue #1886
2025-05-04 18:06:52 +02:00
Alain Nussbaumer
4ecb19724a [web] Remove the lyrics store 2025-05-04 16:51:15 +02:00
Alain Nussbaumer
ae50fe548f [web] Rename formatters library 2025-05-04 12:20:26 +02:00
Alain Nussbaumer
ce3db11cfd [web] Fix styling in mobile mode 2025-05-04 12:12:33 +02:00
Alain Nussbaumer
80b9d8d648 [web] Refactor API calls 2025-05-04 11:36:34 +02:00
Alain Nussbaumer
3677f9d757 [web] Fix camel case 2025-05-02 21:23:28 +02:00
Alain Nussbaumer
895f8376fd [web] Fix camel case for some elements 2025-05-02 21:22:29 +02:00
Alain Nussbaumer
ad143e88c2 [web] Update versions of libraries 2025-05-02 20:12:19 +02:00
Alain Nussbaumer
f037635042 [web] Fix spacing of radio buttons 2025-05-02 20:08:28 +02:00
Alain Nussbaumer
4577c7ace3 [web] Upgrade to latest versions of libraries 2025-04-27 16:57:36 +02:00
Alain Nussbaumer
7dd41179db [web] Update to bulma 1.0.4 2025-04-27 16:50:39 +02:00
Alain Nussbaumer
eade83d381 Merge remote-tracking branch 'origin/master' into web-3.0 2025-04-27 16:12:09 +02:00
Alain Nussbaumer
786d8cbc09 [web] Refactor management of remotes and outputs 2025-04-27 16:10:30 +02:00
Alain Nussbaumer
95de42e6be [web] Ensure field is empty when API call is finished 2025-04-27 09:06:18 +02:00
Alain Nussbaumer
56c9408ef9 [web] Add index for radio stations 2025-04-27 09:05:09 +02:00
Alain Nussbaumer
c38f35d3f9 [web] Add cover artwork for the streams added from the cue 2025-04-27 08:39:33 +02:00
Alain Nussbaumer
bbf7c28349 [web] Change how data is loaded 2025-04-26 21:31:03 +02:00
Alain Nussbaumer
1ce771c900 [web] Upgrade libraries 2025-04-26 08:21:26 +02:00
Alain Nussbaumer
2b6a756740 [web] Simplify linter configuration 2025-04-26 08:20:39 +02:00
Alain Nussbaumer
6091ae31aa [web] Remove unused parameters 2025-04-26 08:20:17 +02:00
Alain Nussbaumer
40c658cb8b [web] Change the way the api is called 2025-04-26 08:18:32 +02:00
ejurgensen
23c67a3eb6 [api] Fix setting output selected and volume at the same time
player_speaker_enable returned 1 on success, but code expected 0. With these
changes the implementation should be more resilient.

Closes #1884
2025-04-25 16:01:16 +02:00
Alain Nussbaumer
180f7393a4 [web] Fix security warnings 2025-04-18 18:03:57 +02:00
Alain Nussbaumer
830b40cc40 [web] Update library versions 2025-04-18 17:50:40 +02:00
ejurgensen
fc071513f9 [docs] Add mention of parentheses to Smart Playlist doc
Closes #1880
2025-04-10 16:31:53 +02:00
Alain Nussbaumer
e35afb6474 [web] Fix security warning 2025-04-08 15:52:38 +02:00
Alain Nussbaumer
b923601823 Merge remote-tracking branch 'origin/master' into web-3.0 2025-04-01 11:38:19 +02:00
Alain Nussbaumer
5c89fa0882 Merge remote-tracking branch 'origin/master' into web-3.0 2025-04-01 11:19:50 +02:00
github-actions[bot]
558814f91f [web] Rebuild web interface 2025-04-01 09:19:28 +00:00
Alain Nussbaumer
3b01f0fc64 [web] Fix security warnings 2025-04-01 11:19:01 +02:00
Alain Nussbaumer
82933f0afb [web] Rename component ListIndexButtons for more coherence 2025-04-01 11:16:34 +02:00
ejurgensen
8e9e939e49 [player] Add log message if speaker_set() called with non-existent speaker id
Closes #1879
2025-03-28 22:24:19 +01:00
Alain Nussbaumer
2e4e741e9a [web] Confine the use of VueEternalLoading to one component 2025-03-28 15:36:45 +01:00
Alain Nussbaumer
f5aecdc4a4 [web] Streamline load method names 2025-03-28 00:01:45 +01:00
Alain Nussbaumer
a4e09b27f4 [web] Simplify logic of displaying menus 2025-03-28 00:00:46 +01:00
Alain Nussbaumer
0aedfe1ad4 [web] Format code 2025-03-27 23:57:26 +01:00
Alain Nussbaumer
24017f21f9 [web] Remove useless call to start the progress bar 2025-03-27 15:04:40 +01:00
Alain Nussbaumer
1c5425ba2a [web] Remove ++ operators 2025-03-25 21:24:26 +01:00
Alain Nussbaumer
e7fcf7dd80 [web] Remove notification identifier 2025-03-25 21:21:30 +01:00
Alain Nussbaumer
582074e8ff [web] Consistently return an array instead of an object 2025-03-25 21:20:58 +01:00
Alain Nussbaumer
4f5e702f5d [web] Use pluralization for displaying some texts 2025-03-25 21:19:56 +01:00
Alain Nussbaumer
30323712f9 [web] Fix options not translated correctly 2025-03-25 21:17:12 +01:00
Alain Nussbaumer
2a0badfea4 [web] Fix media kind not displayed correctly 2025-03-25 21:16:36 +01:00
Alain Nussbaumer
42564905e0 [web] Merge from main branch 2025-03-24 09:19:17 +01:00
Alain Nussbaumer
31ccda3f60 [web] Change the visual for the progress of items 2025-03-23 19:27:44 +01:00
Alain Nussbaumer
d8c3631cd7 [web] Fix property name casing 2025-03-22 16:38:10 +01:00
Alain Nussbaumer
0d00df7b7d [web] Remove unused parameter 2025-03-22 16:19:09 +01:00
Alain Nussbaumer
709b60858d [web] Fix empty empty queries to not be saved 2025-03-22 16:05:12 +01:00
Alain Nussbaumer
ed8e5710f3 [web] Fix missing style for read items 2025-03-22 16:04:31 +01:00
ejurgensen
f108e531bb [spotify] Config check for libcurl 7.83, needed for curl_easy_nextheader()
Resolves #1878
2025-03-20 20:33:11 +01:00
ejurgensen
d99342e586 [spotify] Make client_id and secret configurable
Closes #1877
2025-03-20 20:32:24 +01:00
Alain Nussbaumer
be44c0ce9f [web] Simplify pages 2025-03-18 21:54:27 +01:00
Alain Nussbaumer
15e8854349 [web] Improve dialog modal for directory 2025-03-18 21:50:07 +01:00
Alain Nussbaumer
ed266e3b30 [web] Fix color of current item in queue 2025-03-18 20:55:52 +01:00
Alain Nussbaumer
b11865289a [web] Simplify image control 2025-03-18 20:23:35 +01:00
Alain Nussbaumer
2b8bbb774f [web] Remove unused variable 2025-03-18 20:21:58 +01:00
Alain Nussbaumer
357657f586 [web] Remove useless computed property 2025-03-18 12:40:59 +01:00
Alain Nussbaumer
722307653a [web] Simplify search pages 2025-03-18 10:20:42 +01:00
Alain Nussbaumer
9bea7fef7d [web] Simplify search page 2025-03-17 21:05:47 +01:00
Alain Nussbaumer
9b4d04000f [web] Generate static files 2025-03-16 21:28:16 +01:00
Alain Nussbaumer
4b75ab4ae7 [web] Add getters related Lastfm and Spotify in the services store 2025-03-16 21:26:09 +01:00
Alain Nussbaumer
fa1f10fae9 [web] Change to div elements 2025-03-16 21:17:46 +01:00
ejurgensen
13a8f71c0c [parsers] Fix possible stack overflow from recursion
Closes #1873
2025-03-15 20:16:41 +01:00
Alain Nussbaumer
59b680db9b [web] Rename template slots 2025-03-15 15:06:45 +01:00
Alain Nussbaumer
9e2c9fddcb [web] Refactor hero template 2025-03-15 13:55:14 +01:00
Alain Nussbaumer
2e38df1c40 [web] Avoid styling at page level 2025-03-15 10:38:38 +01:00
Alain Nussbaumer
3cb26a8b77 [web] Add title option for button control 2025-03-15 10:36:44 +01:00
Alain Nussbaumer
a968401d9f [web] Streamline naming of components 2025-03-15 10:36:17 +01:00
Alain Nussbaumer
6481d6f0ee [web] Streamline title bar of modal dialogs 2025-03-15 08:20:21 +01:00
Alain Nussbaumer
7e8672917e [web] Switch to camel case 2025-03-13 23:29:06 +01:00
Alain Nussbaumer
6c09457e5d Merge remote-tracking branch 'origin/master' into web-3.0 2025-03-11 19:49:22 +01:00
github-actions[bot]
8d2b4b925d [web] Rebuild web interface 2025-03-11 18:48:38 +00:00
Alain Nussbaumer
7d15faff66 [web] Fix security warnings 2025-03-11 19:48:09 +01:00
Alain Nussbaumer
444ac4342d [web] Update versions of libraries 2025-03-11 19:45:03 +01:00
Alain Nussbaumer
da30c338fc [web] Change to camel case. 2025-03-11 19:42:17 +01:00
Alain Nussbaumer
905d0ca88b [web] Transition to camel case 2025-03-08 22:27:06 +01:00
Alain Nussbaumer
c22372daa6 [web] Lint code 2025-03-08 21:31:25 +01:00
Alain Nussbaumer
966d563418 [web] Move grouping options to computed properties 2025-03-08 12:13:12 +01:00
Alain Nussbaumer
368bb18aa8 [web] Fix translation key 2025-03-08 12:02:15 +01:00
Alain Nussbaumer
23f624aec5 [web] Fix translation key 2025-03-07 20:56:58 +01:00
Alain Nussbaumer
eecc915a6a [web] Simplify translations 2025-03-05 21:31:48 +01:00
Alain Nussbaumer
296dbe7a78 [web] Fix formatting 2025-03-05 19:27:06 +01:00
Alain Nussbaumer
aa9ca1b4ae [web] Fix badly converted buttons 2025-03-05 19:26:36 +01:00
Alain Nussbaumer
0d981d0ae1 [web] Change link to button 2025-03-05 19:24:51 +01:00
Alain Nussbaumer
1c79273d76 [web] Streamline styles for future refactoring 2025-03-05 19:24:10 +01:00
Alain Nussbaumer
f47a636b99 [web] Add missing translation 2025-03-04 19:28:07 +01:00
Alain Nussbaumer
9af5e74047 Merge remote-tracking branch 'origin/master' into web-3.0 2025-03-02 19:48:27 +01:00
Alain Nussbaumer
4fd02c36ef [web] Generate web assets 2025-03-02 19:48:25 +01:00
Alain Nussbaumer
4a616b7e10 [web] Avoid properties having a value of zero to be displayed 2025-03-02 19:47:08 +01:00
Alain Nussbaumer
bdad6d61bf [web] Refactor the heading title in the pages 2025-03-02 18:03:17 +01:00
Alain Nussbaumer
ef3e64b9c9 [web] Fix page audiobook artist 2025-03-02 00:36:42 +01:00
Alain Nussbaumer
0b86cc18c7 [web] Fix issue preventing items to be played 2025-03-01 22:41:24 +01:00
Alain Nussbaumer
43f4a23b1e [web] Use "button" tag instead of "a" tag 2025-03-01 22:03:28 +01:00
Alain Nussbaumer
ad2ec2252f [web] Fix number formatting on search page 2025-03-01 20:10:53 +01:00
Alain Nussbaumer
f94763d985 [web] Fix French translation not displaying the number of tracks 2025-03-01 19:33:17 +01:00
Alain Nussbaumer
d137b8d157 [web] Rename formatting methods and fix dates not being internationalised 2025-03-01 18:34:35 +01:00
Alain Nussbaumer
173e3fb8a1 [web] Update to newer library versions 2025-03-01 08:10:38 +01:00
Alain Nussbaumer
fef602de2f Merge remote-tracking branch 'origin/master' into web-3.0 2025-03-01 08:01:27 +01:00
Alain Nussbaumer
4694d46508 [web] Simplify the declaration of locales 2025-03-01 07:52:48 +01:00
Alain Nussbaumer
173139515f [web] Hide quality for Spotify tracks 2025-03-01 07:51:46 +01:00
Christian Meffert
aab6f6c718 [logger] Support logging with logfmt format 2025-02-25 20:08:11 +01:00
Alain Nussbaumer
b068b5f745 [web] Replace formatting method with the one provided by Vue-i18n 2025-02-25 17:15:57 +01:00
github-actions[bot]
07afe390f7 [web] Rebuild web interface 2025-02-25 12:27:30 +00:00
Alain Nussbaumer
f587a78e22 [web] Fix security issue 2025-02-25 13:26:53 +01:00
Alain Nussbaumer
68a5254efb [web] Update generated static assets 2025-02-25 13:22:48 +01:00
Alain Nussbaumer
069c00ce30 [web] Use OwnTone information when opening a Spotify album or artist from the music page 2025-02-25 13:21:51 +01:00
Alain Nussbaumer
51b76d0b73 [web] Fix security issue 2025-02-25 13:20:26 +01:00
Alain Nussbaumer
bc2fa02589 [web] Simplify translations 2025-02-24 21:27:04 +01:00
ejurgensen
3682fdb269
Merge pull request #1855 from owntone/spotify_proto1
Support for Spotify's http based protocol
2025-02-24 20:01:08 +01:00
ejurgensen
4980218fc5 [spotify] Import version 0.5 of librespot-c 2025-02-24 19:58:09 +01:00
ejurgensen
038c741052 [spotify] Set the new protocol as experimental and fallback, old one as default 2025-02-23 23:43:51 +01:00
ejurgensen
2547336576 [spotify] Retry fix for connection retry 2025-02-23 23:27:50 +01:00
ejurgensen
aab49945d4 [misc] Distinguished logging of errors from getaddrinfo() 2025-02-23 23:27:50 +01:00
ejurgensen
b5977b5633 [spotify] Fix for connection retry during login 2025-02-23 23:27:50 +01:00
ejurgensen
3f9e400dbd [spotify] Import version 0.4 of librespot-c and remove password-based login
Experimental version to test new protocol
2025-02-23 23:27:49 +01:00
Alain Nussbaumer
deeeaad3b4 Merge remote-tracking branch 'origin/master' into web-3.0 2025-02-23 20:15:22 +01:00
Alain Nussbaumer
3eb9a72073 [web] Build static artifacts 2025-02-23 19:38:52 +01:00
Alain Nussbaumer
f8cc5edea4 [web] Fix hero page alignment of image 2025-02-23 19:38:21 +01:00
Alain Nussbaumer
b8bd0ee847 [web] Fix alignment of item when the queue is empty 2025-02-23 19:18:43 +01:00
Alain Nussbaumer
09c83768b1 [web] Simplify styling of the now playing page 2025-02-23 19:11:37 +01:00
Alain Nussbaumer
c13e6ad672 [web] Reposition item count in pages 2025-02-23 19:11:12 +01:00
Alain Nussbaumer
bf2e468350 [web] Simplify styling of hero page 2025-02-23 19:04:20 +01:00
Alain Nussbaumer
acbc335897 [web] Close the menu when the volume menu is opened 2025-02-23 18:34:54 +01:00
Alain Nussbaumer
675b090c0b [web] Change the background behind modal dialogues and menus 2025-02-23 17:31:16 +01:00
Alain Nussbaumer
1db7b53df0 [web] Format source code 2025-02-23 17:26:12 +01:00
Alain Nussbaumer
d09cdfe582 [web] Remove the shadow around the modal dialogue 2025-02-23 17:25:20 +01:00
Alain Nussbaumer
35ac036d92 [web] Set light colour to navigation bar 2025-02-23 17:20:17 +01:00
Alain Nussbaumer
1fbaf96ebd [web] Remove gradient style on album page 2025-02-23 17:19:42 +01:00
Alain Nussbaumer
b01e644ccf [web] Fix the navigation bar colour in dark theme 2025-02-23 17:19:08 +01:00
Alain Nussbaumer
17d48a379a [web] Change logical order of main page elements 2025-02-23 17:18:40 +01:00
Alain Nussbaumer
b578926d77 [web] Reduce size of the switch control 2025-02-23 15:38:30 +01:00
Alain Nussbaumer
05d9447c3c [web] Simplify the navigation bar 2025-02-23 12:41:51 +01:00
Alain Nussbaumer
56aee6ad05 [web] Hide tab label only if there is an icon 2025-02-23 12:24:07 +01:00
Alain Nussbaumer
369e27fbed [web] Fix update dialog to not show the analyse action 2025-02-23 12:23:06 +01:00
Alain Nussbaumer
45f7defa09 [web] Simplify the about page 2025-02-22 11:22:29 +01:00
ejurgensen
7eac8adb83 Bump to version 28.12 2025-02-22 09:07:03 +01:00
ejurgensen
8cfb3db6dd ChangeLog for OwnTone 28.12 2025-02-22 09:06:02 +01:00
Alain Nussbaumer
222e6ea2d7 [web] Allow clicking on label to change switch state 2025-02-20 20:40:25 +01:00
Christian Meffert
9b56727361
[spotify] Fix possible deadlock during Spotify scan (#1859) 2025-02-20 07:13:55 +01:00
Alain Nussbaumer
0c9a803b53 [web] Fix dialog not closing 2025-02-19 21:19:03 +01:00
ejurgensen
d64c30b5ab [logger] Fix more compile warnings on e.g. ARMv7 (ref #1863) 2025-02-18 20:17:59 +01:00
ejurgensen
200d9abc43 [logger] Fix compile warnings on e.g. ARMv7 (closes #1863) 2025-02-18 19:30:30 +01:00
Christian Meffert
5acf9dd336 [db] Add thread name/id to db wait unlock 2025-02-16 23:51:06 +01:00
Christian Meffert
5fec4bbe34 [logger] Add thread name and thread id to log messages 2025-02-16 23:51:06 +01:00
Alain Nussbaumer
b460be8873 [web] Refactor input field for URLs 2025-02-16 20:42:08 +01:00
Alain Nussbaumer
b5c7dfaf59 [web] Fix scrolling problem when dialog is not closed properly 2025-02-16 20:06:40 +01:00
ejurgensen
7cde752f20
Merge pull request #1862 from hacketiwack/master
[api] Stop playback once last item is removed from the queue
2025-02-16 16:24:27 +01:00
Alain Nussbaumer
d16373d711 [web] Fix missing input field check 2025-02-16 15:40:40 +01:00
Alain Nussbaumer
05b0def840 [web] Rename component 2025-02-16 15:36:31 +01:00
Alain Nussbaumer
3091290677 [web] Replace icon for hide action 2025-02-16 15:32:28 +01:00
Alain Nussbaumer
d26e9fd5c4 [web] Reduce the amount of colours 2025-02-16 15:30:37 +01:00
Alain Nussbaumer
59559847ac [web] Use kebab style case for tags 2025-02-16 15:29:19 +01:00
Alain Nussbaumer
c72dc48b1d [api] Stop playback once last item is removed from the queue 2025-02-16 12:37:58 +01:00
Alain Nussbaumer
330023c940 [web] Fix playlist save not working 2025-02-16 11:07:33 +01:00
github-actions[bot]
1abc75362e [web] Rebuild web interface 2025-02-16 09:40:19 +00:00
Alain Nussbaumer
3d27b1c25a
Merge pull request #1860 from fomojola/patch-1
[web] Add missing fields to configuration store
2025-02-16 10:39:57 +01:00
fomojola
66de2f4a96
Update configuration.js to add missing state fields
Added the 2 missing config fields to the configurationStore. On v28.11, the absence of these 2 fields was preventing the queue save as playlist button from appearing, even though the /config endpoint was returning the right fields.
2025-02-16 00:46:35 -05:00
Alain Nussbaumer
aaea7135a0 [web] Streamline the style of button 2025-02-16 00:17:55 +01:00
Alain Nussbaumer
f54b2e5b0b [web] Fix tab list control to display icons when none are given 2025-02-15 23:31:30 +01:00
Alain Nussbaumer
55720b8182 [web] Fix dropdown control being too wide on mobile 2025-02-15 23:30:34 +01:00
Alain Nussbaumer
eca8f40afc [web] Renaming image control 2025-02-15 22:55:01 +01:00
Alain Nussbaumer
26a03a1219 Merge remote-tracking branch 'origin/master' into web-3.0 2025-02-15 22:29:27 +01:00
Alain Nussbaumer
8526268d70 [web] Remove deprecated directives 2025-02-15 22:28:42 +01:00
Alain Nussbaumer
a82c80eb65 [web] Create control for tab list 2025-02-15 22:28:23 +01:00
Alain Nussbaumer
6b0e57c221 [web] Fix spacing of pages with tabs 2025-02-15 22:00:31 +01:00
Alain Nussbaumer
ed81d32ab3 [web] Update versions of libraries 2025-02-15 20:16:45 +01:00
Alain Nussbaumer
c5cb67ff07 [web] Fix spacing 2025-02-15 20:16:20 +01:00
Alain Nussbaumer
570c663178 [web] Remove button section when not present 2025-02-15 20:15:58 +01:00
Alain Nussbaumer
b477121dda [web] Fix alignement in switch control 2025-02-15 20:14:22 +01:00
Alain Nussbaumer
24d2204fb0 [web] Fix colours 2025-02-15 20:13:12 +01:00
Alain Nussbaumer
cb74bc17be [web] Fix spacing when no buttons in properties 2025-02-15 12:01:43 +01:00
Alain Nussbaumer
30b9653c69 [web] Fix spacing of elements 2025-02-15 12:00:52 +01:00
Alain Nussbaumer
110de022a1 [web] Remove useless class 2025-02-15 11:18:23 +01:00
Alain Nussbaumer
cc5ef9bce8 [web] Remove useless attribute 2025-02-15 11:16:30 +01:00
Alain Nussbaumer
555c08cfbe [web] Remove specific styles 2025-02-15 11:02:34 +01:00
Alain Nussbaumer
317df2454d [web] Remove specific styles 2025-02-15 10:09:08 +01:00
Alain Nussbaumer
b7af43873b [web] Bump versions of libraries 2025-02-13 21:02:35 +01:00
Alain Nussbaumer
a93777aeec [web] Fix bug when adding playable items to queue 2025-02-13 21:02:13 +01:00
Alain Nussbaumer
d6d5912de1 [web] Refactor list of properties 2025-02-12 19:13:35 +01:00
Alain Nussbaumer
97d0e90408 [web] Simplify property translations 2025-02-12 18:50:22 +01:00
Alain Nussbaumer
aae2904a57 [web] Fix missing buttons on dialogs 2025-02-11 19:05:15 +01:00
ejurgensen
792e04b47d [artwork] Allow longer tokens in auth headers towards online sources
Fixes "Cannot make request for online artwork, auth header is too long" error
2025-02-11 16:44:47 +01:00
Alain Nussbaumer
6f818b917c [web] Update versions of library 2025-02-09 18:13:41 +01:00
Alain Nussbaumer
fcb8d67859 [web] Fix invalid dates 2025-02-09 18:12:59 +01:00
Alain Nussbaumer
e3c8d1fab9 [web] Refactor modal dialogs 2025-02-09 17:52:45 +01:00
Alain Nussbaumer
b9b36855f4 [web] Remove unused events 2025-02-09 11:13:51 +01:00
Alain Nussbaumer
ea7efdd869 [web] Refactor modal dialog for playable items 2025-02-09 11:13:23 +01:00
Alain Nussbaumer
714fc4e1b8 [web] Fix emit warnings 2025-02-09 08:05:03 +01:00
Alain Nussbaumer
bb8b2a72e4 [web] Simplify modal dialogs 2025-02-08 14:35:25 +01:00
Alain Nussbaumer
a1e68a1aa6 [web] Simplify the modal dialog with actions 2025-02-08 14:27:54 +01:00
Alain Nussbaumer
8e82fc9f9f [web] Remove useless style 2025-02-08 11:17:31 +01:00
Alain Nussbaumer
aa14e0ea4d [web] Simplify multiline texts in translations 2025-02-08 08:51:37 +01:00
Alain Nussbaumer
461d5497cf [web] Fix missing cancel icon 2025-02-08 08:30:20 +01:00
Alain Nussbaumer
76d517ecbb [web] Remove non-standard cursor 2025-02-08 07:47:51 +01:00
Alain Nussbaumer
612c62dcb8 [web] Remove non-standard icon 2025-02-08 07:32:05 +01:00
Alain Nussbaumer
6354f9972a [web] Remove remnants of the name forked-daapd in styles 2025-02-07 21:42:31 +01:00
Alain Nussbaumer
4796963781 Merge remote-tracking branch 'origin/master' into bulma-1.0 2025-02-07 20:06:51 +01:00
ejurgensen
5cbc520712 [docs] Add note that "radio_playlists" config option is for DAAP only
Closes #1850
2025-02-05 09:34:16 +01:00
ejurgensen
71d48452d8 [mpd] Fix bug in 28.11 (and .10?) where "mpc enable/disable" toggles
Fixes #1847
2025-02-05 09:16:26 +01:00
Alain Nussbaumer
e9ed220853 [sync] Merge remote-tracking branch 'origin/master' into bulma-1.0 2025-02-04 22:08:54 +01:00
Alain Nussbaumer
8140e008f0 [web] Fix styles to comply with Bulma 1.0 2025-02-04 22:00:48 +01:00
ejurgensen
591a0b6b83 [spotify] Allow for longer tokens (fixes #1846) 2025-02-04 19:39:25 +01:00
Christian Meffert
991ed0e765
Merge pull request #1843 from chme/feat/listenbrainz
feat: Support scrobbling played songs to ListenBrainz
2025-02-02 14:59:49 +01:00
Christian Meffert
7760554cb7 [lbrainz] Add ListenBrainz integration
- Support submitting listens (scrobble) to ListenBrainz.
- Add JSON API endpoints to manage ListenBrainz auth token.
2025-02-01 14:25:45 +01:00
Christian Meffert
9283d0c3c2 [http] Make output_body (request body) in http_client_ctx "const" and delete unused fields 2025-02-01 14:25:10 +01:00
github-actions[bot]
f6ffa321bb [web] Rebuild web interface 2025-01-30 19:34:14 +00:00
Alain Nussbaumer
1d529e436f
[web] Fix cover artwork not being displayed in album lists #1848 2025-01-30 20:33:48 +01:00
Christian Meffert
6fbf1f0ac5
Merge pull request #1844 from chme/docs
[docs] Publish changelog to gh-pages
2025-01-26 18:00:42 +01:00
Christian Meffert
2e019690cd [docs] Publish changelog to gh-pages
- Format ChangeLog in Markdown.
- Publish ChangeLog as part of the mkdocs documentation to gh-pages.
- Fix formatting of sub lists on docs index page.
2025-01-26 15:05:28 +00:00
ejurgensen
01dbda3ec0 Bump to version 28.11 2025-01-26 11:21:13 +01:00
ejurgensen
e51cb9e71d ChangeLog for OwnTone 28.11 2025-01-26 11:19:43 +01:00
Alain Nussbaumer
b2fbbd3fa0 [web] Switch to Bulma 1.0 2025-01-25 09:28:08 +01:00
github-actions[bot]
12321a30da [web] Rebuild web interface 2025-01-25 08:17:15 +00:00
Alain Nussbaumer
b79d0c9f9f [web] Fix security warning 2025-01-25 09:16:32 +01:00
github-actions[bot]
d6e24f9117 [web] Rebuild web interface 2025-01-23 08:33:20 +00:00
ejurgensen
8a177ed48d Revert "Merge branch 'master' of github.com:owntone/owntone-server"
This reverts commit bb2a778b46afca0fcd56bda6f02857fad78b14d8, reversing
changes made to f8e2298b677476f46eb67cc4331c18124f92a791.
2025-01-23 09:31:51 +01:00
ejurgensen
614bcaa630 Reapply "Merge branch 'master' of github.com:owntone/owntone-server"
This reverts commit 7ba083bf8b19bda5abb1e1476666632fa76452e3.
2025-01-23 09:31:12 +01:00
github-actions[bot]
6895df17ac [web] Rebuild web interface 2025-01-22 22:44:58 +00:00
ejurgensen
7ba083bf8b Revert "Merge branch 'master' of github.com:owntone/owntone-server"
This reverts commit bb2a778b46afca0fcd56bda6f02857fad78b14d8, reversing
changes made to a4cc4fde8cc5457936e67e95ce8dcee4f58a441b.
2025-01-22 23:42:46 +01:00
github-actions[bot]
8b05411427 [web] Rebuild web interface 2025-01-22 19:33:46 +00:00
Alain Nussbaumer
bb2a778b46 Merge branch 'master' of github.com:owntone/owntone-server 2025-01-22 20:32:53 +01:00
ejurgensen
a4cc4fde8c [configure] Fix AX_PROG_FLEX/AC_PROG_LEX warning (#1839) 2025-01-22 20:28:27 +01:00
Alain Nussbaumer
ca865ecbe0 [configure] Fix AC_TRY_LINK warning (#1839) 2025-01-22 20:28:27 +01:00
github-actions[bot]
afee011fe8 [web] Rebuild web interface 2025-01-22 20:28:27 +01:00
Alain Nussbaumer
e83ad3e3d2 [web] Update versions of libraries 2025-01-22 20:28:27 +01:00
github-actions[bot]
01ca2edc96 [web] Rebuild web interface 2025-01-22 20:27:34 +01:00
Alain Nussbaumer
386ad61bc8 [web] Simplify WebSocket creation and remove unused translation 2025-01-22 20:27:34 +01:00
Christian Meffert
9420c52bf7 [docs] Docs for development setup 2025-01-22 20:27:34 +01:00
Christian Meffert
2fe6969f72 [dev] Add devcontainers and vscode config templates 2025-01-22 20:27:34 +01:00
Christian Meffert
9fbd07a75d [db] Add cli parameter to overwrite path to SQLite extension
This will allow pointing to the locally build (and not installed) owntone SQLite extension during development and local testing.
2025-01-22 20:27:34 +01:00
github-actions[bot]
ce3c617023 [web] Rebuild web interface 2025-01-22 20:27:34 +01:00
Christian Meffert
a2d56df416 [httpd] Fix infinite loop with more than one ws connection 2025-01-22 20:27:34 +01:00
Christian Meffert
c2ad43da93 [web] Fix linter error 2025-01-22 20:27:34 +01:00
Christian Meffert
a454f062bb [web] Implement libevent ws in httpd_libeventhttp, address thread safety 2025-01-22 20:27:34 +01:00
Christian Meffert
91175dc905 [listener] Support passing context arg to listener callbacks 2025-01-22 20:27:34 +01:00
Christian Meffert
eca99f120a [httpd] libevent proxy handling changes with libevent >= 2.2 2025-01-22 20:27:34 +01:00
Christian Meffert
a3ab301cff [web] Support libevent as WS server instead of libwebsockets
If libevent >= 2.2 is detected during configure and "websocket_port" == 0 in the config file, the libwebsocket implementation is disabled and instead the libevent http server offers the websocket connection. The connection to the websocket is then done with the path "/ws".
2025-01-22 20:27:34 +01:00
github-actions[bot]
fd322a2941 [web] Rebuild web interface 2025-01-22 20:27:34 +01:00
Alain Nussbaumer
a51da62ca4 [web] Add streamurl_ignore setting under artwork tab 2025-01-22 20:27:34 +01:00
ejurgensen
c7432e6bca [settings] Move two settings to artwork category
show_cover_artwork_in_album_lists and streamurl_ignore. Ref #1829.
2025-01-22 20:26:06 +01:00
ejurgensen
410d3a0cfa [http] Implement setting to ignore StreamUrl
Some radio stations send garbage in the ICY StreamUrl tag, ref issue #1829.
2025-01-22 20:26:06 +01:00
ejurgensen
76b5da0f0d [settings] Make settings convenience macros more convenient 2025-01-22 20:26:06 +01:00
ejurgensen
f986eedb25 [artwork] Fix concurrency issues with online search and artwork retrieval 2025-01-22 20:26:06 +01:00
ejurgensen
464f87a8db [artwork] Don't make online artwork searches if artist/album starts with "unknown" 2025-01-22 20:26:06 +01:00
ejurgensen
3c98ca7928 [http] Support for extracting artwork url from within StreamUrl field 2025-01-22 20:26:06 +01:00
github-actions[bot]
6c7b568e49 [web] Rebuild web interface 2025-01-22 20:26:06 +01:00
羽先生
795c27bdf9 Improved Simplified Chinese i18n
the "簡體中文" should be "简体中文"
2025-01-22 20:26:06 +01:00
ejurgensen
e46bf3db0c [docs] Remove link to unmaintained docker-daapd 2025-01-22 20:26:06 +01:00
github-actions[bot]
53984fec46 [web] Rebuild web interface 2025-01-22 20:26:06 +01:00
Scott Shambarger
2e10ef4266 Correct libxml2 devel requires in spec file 2025-01-22 20:26:06 +01:00
ejurgensen
1b2c51bc5e [misc] Improve logging of int conversion errors 2025-01-22 20:26:06 +01:00
github-actions[bot]
d15c91a240 [web] Rebuild web interface 2025-01-22 20:26:06 +01:00
ejurgensen
9f1a1b3c14 [docs] Reduce doc duplication between configuration.md and actual config file
Link to the config file instead of repeating documentation, since keeping the
two in sync is cumbersome maintaincewise.
2025-01-22 20:26:06 +01:00
aaronk6
8003199fa4 [docs] Change cache_path to cache_dir 2025-01-22 20:26:06 +01:00
ejurgensen
bb80ca1c62 [docs] Document changing track attribs time_played/time_skipped 2025-01-22 20:26:06 +01:00
ejurgensen
1810be023d [json-api] Setting of track attribs time_played/time_skipped and make generic 2025-01-22 20:26:06 +01:00
ejurgensen
40f43840f2 [httpd] Fixup commit 5d7e3dc0 in case NULL isn't zero 2025-01-22 20:26:06 +01:00
ejurgensen
d0ff361ae0 [httpd] Add parameter "no_register_playback" for DAAP/RSP streaming 2025-01-22 20:26:06 +01:00
ejurgensen
9a513e41c7 [json_api] Method for setting skip_count and for setting play_count directly 2025-01-22 20:26:06 +01:00
ejurgensen
66d52d06ab [docs] Update Chromecast note about Nest Hub 2025-01-22 20:26:06 +01:00
ejurgensen
e43afc853a [docs] Update Makefile.am with updated doc sources 2025-01-22 20:26:06 +01:00
ejurgensen
fba51fb93b [docs] Brush up the documentation
* Try to organize after expected user need
* Update mkdocs stuff and fix a broken link
* Move web building info to building.md
* Add info about Roku Soundbridge
* Add note about Google Nest not working
2025-01-22 20:26:06 +01:00
ejurgensen
72ef58d853 [mpd] Add getvol, bump version to 0.23.0 2025-01-22 20:26:06 +01:00
ejurgensen
5571fa628d [mpc] Support for readpicture and albumart 2025-01-22 20:26:06 +01:00
ejurgensen
2619f45ab5 [gh-actions] Add FreeBSD build workflow using vmactions 2025-01-22 20:26:06 +01:00
ejurgensen
df86df02dc [daap] Fix for Apple Music/iTunes not working on Airplay host (attempt 2)
OwnTone selects ALAC for daap streaming because it thinks the client is an Airplay speaker.

Regression in 28.10 coming from PR #1698.

Fixes #1816
2025-01-22 20:26:06 +01:00
ejurgensen
04bca06805 Revert "[daap] Fix for Apple Music/iTunes not working on Airplay host"
This reverts commit 73864e82cd267da65b7245430bd1a2060d8bec81.
2025-01-22 20:26:06 +01:00
ejurgensen
7ed66079f2 [daap] Fix for Apple Music/iTunes not working on Airplay host
OwnTone selects ALAC for daap streaming because it thinks the client is an Airplay speaker.

Regression in 28.10 coming from PR #1698.

Fixes #1816
2025-01-22 20:26:06 +01:00
ejurgensen
3999449c89 [httpd/daap] Improve logging of speaker profile selection 2025-01-22 20:26:06 +01:00
ejurgensen
0483aad095 [mpd] Fix outputs enable/disable/toggle not working in v28.10
Also simplify code by using new speaker index from player and remove some code
duplication.

Regression from PR #1779. Fixes #1814.
2025-01-22 20:26:06 +01:00
ejurgensen
269aed632f [player] Add a index attribute to struct player_speaker_info + a getter 2025-01-22 20:26:06 +01:00
github-actions[bot]
6eb5667b4a [web] Rebuild web interface 2025-01-22 20:26:06 +01:00
Fabian Groffen
1019b777c3 [mpd] outputs: drop invalid outputsvolume key
This key confuses some clients, and it isn't emitted by MPD, nor
documented to exist:
9ff8e02e54/src/output/Print.cxx

It was added in bdb2c7493, but without explanation why it was added to
outputs command.

Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2025-01-22 20:22:10 +01:00
ejurgensen
d57b7565da [mpd] Add Bison/Flex parser for complex mpd commands + misc
Some of the misc:
* Initialize query_params from the start of each function
* Reduce code duplication by consolidating the handler's integer conversion
* Go back to classic int return types for handlers
* Change list grouping to respond like mpd
* Sanitize all user string output
2025-01-22 20:22:10 +01:00
ejurgensen
08b1b74ddd [mpd] Make protocol version a #define 2025-01-22 20:22:10 +01:00
ejurgensen
40f2bb5d81 [db] free_query_params should also free qp->group
Plus honor qp->order in queue_enum_start() if set by caller
2025-01-22 20:22:10 +01:00
ejurgensen
ed526f7fe3 [mpd] Refactor commit 9962c743 and other stuff in mpd.c 2025-01-22 20:22:10 +01:00
gd
277e5001a0 [mpd] Fix: allow password command when not authenticated 2025-01-22 20:22:10 +01:00
gd
b46a035a2f [mpd,db] MPD protocol fixes to handling of idle/noidle command and command list.
Command handling:
1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received
command line.
2. mpd_process_line() handles idle state and command list state and delegates
to mpd_process_command() to handle each command line.
If the command was successful it sends OK to the client according the the
command list state.
Error responses are sent by mpd_process_command().
3. mpd_process_command() parses the args and delegates to the individual
command handler.

mpd_input_filter:
1. Removed handling of command lists. They are handled by mpd_process_line().
2. Return BEV_OK if there's at least one complete line of input even if there's
more data in the input buffer.

Idle/noidle:
1. Changed mpd_command_idle() to never write OK to the output buffer.
Instead it is the responsibility of the caller to decide on the response.

2. Removed mpd_command_noidle() instead it is handled in mpd_process_line().
If the client is not in idle state noidle is ignored (no response sent)
If the client is in idle state then it changes idle state to false and sends
OK as the response to the idle command.

Command lists:
1. Added command list state to the client context so commands in the list are
buffered and only executed after receiving command_list_end.

Connection state:
1. Added is_closing flag in the client context to ignore messages received
after freeing the events buffer in intent to close the client connection.

Command arguments parsing:
1. Updated COMMAND_ARGV_MAX to 70 to match current MPD.
2. Changed mpd_pars_range_arg to handle open-ended range.

Command pause:
1. pause is ignored in stopped state instead returning error.

Command move:
1. Changed mpd_command_move() to support moving a range.
2. Added db_queue_move_bypos_range() to support moving a range.

Command password:
1. Password authentication flag set in handler mpd_command_password() instead
of in command processor.

Config:
1. Added config value: "max_command_list_size".
   The maximum allowed size of buffered commands in command list.
2025-01-22 20:22:10 +01:00
ejurgensen
1e0fffc624 Bump to version 28.10 2025-01-22 20:22:10 +01:00
ejurgensen
3093bfca62 ChangeLog for OwnTone 28.10 2025-01-22 20:22:10 +01:00
ejurgensen
f1b4ff8cc7 [mpd] Set protocol version back 0.20.0
Announcing 0.22.4 without supporting the newer protocol's filters breaks Rigelian
2025-01-22 20:22:10 +01:00
ejurgensen
f8e2298b67 [configure] Fix AX_PROG_FLEX/AC_PROG_LEX warning (#1839) 2025-01-20 09:56:40 +01:00
Alain Nussbaumer
b50616a065
[configure] Fix AC_TRY_LINK warning (#1839) 2025-01-19 17:00:58 +01:00
github-actions[bot]
7c6252a108 [web] Rebuild web interface 2025-01-19 14:08:11 +00:00
Alain Nussbaumer
d93f51a6cc [web] Update versions of libraries 2025-01-19 15:07:45 +01:00
github-actions[bot]
14766e63cb [web] Rebuild web interface 2025-01-18 15:54:32 +00:00
Alain Nussbaumer
686a453fc5 [web] Simplify WebSocket creation and remove unused translation 2025-01-18 16:54:02 +01:00
Christian Meffert
11fc7d3962
Merge pull request #1828 from chme/feat/devcontainer
dev: Add template for VSCode settings and devcontainers
2025-01-18 10:34:22 +01:00
github-actions[bot]
8aaafe0fc3 [web] Rebuild web interface 2025-01-18 06:12:34 +00:00
Christian Meffert
8c2b44fc6c
Merge pull request #1818 from chme/feat/libevent-2.2-ws
[web] Support libevent as WS server instead of libwebsockets
2025-01-18 07:12:12 +01:00
github-actions[bot]
f4ac6f9c1c [web] Rebuild web interface 2025-01-17 16:37:01 +00:00
ejurgensen
450a333fd6
Merge pull request #1836 from owntone/streamurl_direct2
Streamurl options and artwork fixes, see issue #1829
2025-01-17 17:36:37 +01:00
Alain Nussbaumer
fbdc114288 [web] Add streamurl_ignore setting under artwork tab 2025-01-17 17:27:18 +01:00
ejurgensen
2497fe89df [settings] Move two settings to artwork category
show_cover_artwork_in_album_lists and streamurl_ignore. Ref #1829.
2025-01-17 17:26:19 +01:00
ejurgensen
d9cd0f4142 [http] Implement setting to ignore StreamUrl
Some radio stations send garbage in the ICY StreamUrl tag, ref issue #1829.
2025-01-17 17:21:42 +01:00
ejurgensen
504c056c87 [settings] Make settings convenience macros more convenient 2025-01-17 17:21:42 +01:00
ejurgensen
dbaeb7bdca [artwork] Fix concurrency issues with online search and artwork retrieval 2025-01-17 17:21:42 +01:00
ejurgensen
158b043f55 [artwork] Don't make online artwork searches if artist/album starts with "unknown" 2025-01-17 17:21:42 +01:00
ejurgensen
024aadfe0a [http] Support for extracting artwork url from within StreamUrl field 2025-01-17 17:21:42 +01:00
github-actions[bot]
a93352d993 [web] Rebuild web interface 2025-01-15 18:14:22 +00:00
Alain Nussbaumer
383a58d546
Merge pull request #1835 from VergilGao/patch-1
[web] Improve simplified Chinese translation
2025-01-15 19:13:59 +01:00
ejurgensen
ad3be7d2f9
[docs] Remove link to unmaintained docker-daapd 2025-01-13 17:36:25 +01:00
羽先生
4c5888e8a8
Improved Simplified Chinese i18n
the "簡體中文" should be "简体中文"
2025-01-13 09:50:24 +08:00
Alain Nussbaumer
289e7dcdce [web] Set new version for the web UI 2025-01-04 04:57:54 +01:00
Alain Nussbaumer
6ee5911729 [web] Fix option menu opening 2025-01-03 21:12:11 +01:00
Alain Nussbaumer
fde798d5f5 [web] Set new version for the web UI 2025-01-03 21:11:43 +01:00
Alain Nussbaumer
b922a6a1a1 [web] Align items in volume menu 2025-01-01 22:10:13 +01:00
Alain Nussbaumer
44896d82f4 [web] Simplify the display of lists 2025-01-01 21:28:30 +01:00
Alain Nussbaumer
0e3490e589 [web] Remove the non-standard heading class 2025-01-01 21:13:48 +01:00
Alain Nussbaumer
a415e619af [web] Lint source code 2025-01-01 17:36:24 +01:00
Alain Nussbaumer
2d0747dbe9 [web] Fix spacing in lists of items 2025-01-01 17:36:09 +01:00
Alain Nussbaumer
02307e86cd [web] Move styles in the components 2025-01-01 17:22:17 +01:00
Alain Nussbaumer
16a76fdc58 [web] Increment version of the web interface 2024-12-31 10:55:38 +01:00
Alain Nussbaumer
7c8b787afb [web] Add ability to access the server externally 2024-12-31 10:41:18 +01:00
Alain Nussbaumer
e891b5d24c [web] Fix page "Now Playing" progress update when pausing 2024-12-31 10:39:03 +01:00
github-actions[bot]
218d77e070 [web] Rebuild web interface 2024-12-31 09:18:20 +00:00
Alain Nussbaumer
fab5ef505e
Merge pull request #1832 from chme/fix/now-playing-pause
fix: Now Playing does not stop play progress updates when pausing
2024-12-31 10:17:53 +01:00
Christian Meffert
fb067b5cdc fix: Now Playing does not stop play progress updates when pausing 2024-12-31 08:06:04 +01:00
Christian Meffert
d6448ada68 [docs] Docs for development setup 2024-12-31 07:07:11 +01:00
Christian Meffert
46dabafbdc [dev] Add devcontainers and vscode config templates 2024-12-31 07:07:11 +01:00
Alain Nussbaumer
cdbce17731 [web] Remove useless app div element 2024-12-30 07:41:31 +01:00
Alain Nussbaumer
d1c2f0f9fd [web] Fix attribute order 2024-12-30 07:41:03 +01:00
Alain Nussbaumer
66b11c96e2 [web] Update libraries 2024-12-28 05:01:08 +01:00
Christian Meffert
ea9df4f8ee [httpd] Fix infinite loop with more than one ws connection 2024-12-27 18:38:07 +01:00
Christian Meffert
42069405d6 [web] Fix linter error 2024-12-27 14:27:08 +01:00
Christian Meffert
f2e4819565 [web] Implement libevent ws in httpd_libeventhttp, address thread safety 2024-12-27 13:54:50 +01:00
Christian Meffert
27c9224f69 [listener] Support passing context arg to listener callbacks 2024-12-27 13:54:49 +01:00
Christian Meffert
07a5e6858d [httpd] libevent proxy handling changes with libevent >= 2.2 2024-12-27 13:54:49 +01:00
Christian Meffert
c0192cc1f6 [web] Support libevent as WS server instead of libwebsockets
If libevent >= 2.2 is detected during configure and "websocket_port" == 0 in the config file, the libwebsocket implementation is disabled and instead the libevent http server offers the websocket connection. The connection to the websocket is then done with the path "/ws".
2024-12-27 13:51:58 +01:00
Christian Meffert
ef2740a6dd [db] Add cli parameter to overwrite path to SQLite extension
This will allow pointing to the locally build (and not installed) owntone SQLite extension during development and local testing.
2024-12-27 13:17:23 +01:00
Alain Nussbaumer
3c85e540b9 [web] Fix security issue 2024-12-21 11:25:10 +01:00
ejurgensen
4b0e3b260c
Merge pull request #1827 from sshambar/spec-fix 2024-12-19 07:32:32 +01:00
Scott Shambarger
d7cbb46264 Correct libxml2 devel requires in spec file 2024-12-18 19:30:28 -08:00
ejurgensen
83f95e0381
[misc] Improve logging of int conversion errors 2024-12-13 11:25:43 +01:00
github-actions[bot]
2f8b007d32 [web] Rebuild web interface 2024-12-05 00:05:23 +00:00
Alain Nussbaumer
74ce03deed [web] Fix security warnings 2024-12-05 01:04:54 +01:00
Alain Nussbaumer
861a18f4e3 [web] Update library versions 2024-12-05 01:02:42 +01:00
Alain Nussbaumer
a79ce01ae1 [web] Use newer versions of libraries 2024-11-14 10:44:21 +01:00
Alain Nussbaumer
4748e6adb5 [web] Remove background color 2024-11-14 10:42:51 +01:00
ejurgensen
fd0060b199 [docs] Reduce doc duplication between configuration.md and actual config file
Link to the config file instead of repeating documentation, since keeping the
two in sync is cumbersome maintaincewise.
2024-11-12 16:55:24 +01:00
ejurgensen
877efd2aba
Merge pull request #1824 from aaronk6/master
[docs] Change cache_path to cache_dir
2024-11-12 09:03:33 +01:00
aaronk6
b39dfefd72 [docs] Change cache_path to cache_dir 2024-11-11 22:32:55 +01:00
ejurgensen
3b28960675 [docs] Document changing track attribs time_played/time_skipped 2024-10-17 23:42:09 +02:00
ejurgensen
12f728629f [json-api] Setting of track attribs time_played/time_skipped and make generic 2024-10-17 23:31:46 +02:00
ejurgensen
750f83b7e0 [httpd] Fixup commit 5d7e3dc0 in case NULL isn't zero 2024-10-17 19:58:25 +02:00
ejurgensen
5d7e3dc090 [httpd] Add parameter "no_register_playback" for DAAP/RSP streaming 2024-10-17 19:48:14 +02:00
ejurgensen
880f5b2bf6 [json_api] Method for setting skip_count and for setting play_count directly 2024-10-16 16:42:24 +02:00
ejurgensen
8ae25aaf3e
[docs] Update Chromecast note about Nest Hub 2024-10-14 23:09:43 +02:00
ejurgensen
09a70ad993 [docs] Update Makefile.am with updated doc sources 2024-10-11 19:43:06 +02:00
ejurgensen
7da811e3b3 [docs] Brush up the documentation
* Try to organize after expected user need
* Update mkdocs stuff and fix a broken link
* Move web building info to building.md
* Add info about Roku Soundbridge
* Add note about Google Nest not working
2024-10-11 19:29:49 +02:00
ejurgensen
75a1082b0a [mpd] Add getvol, bump version to 0.23.0 2024-10-08 23:43:34 +02:00
ejurgensen
ebec99cc9d [mpc] Support for readpicture and albumart 2024-10-08 20:35:12 +02:00
ejurgensen
601f5a7657 [gh-actions] Add FreeBSD build workflow using vmactions 2024-10-07 23:50:21 +02:00
ejurgensen
81cf713a83 [daap] Fix for Apple Music/iTunes not working on Airplay host (attempt 2)
OwnTone selects ALAC for daap streaming because it thinks the client is an Airplay speaker.

Regression in 28.10 coming from PR #1698.

Fixes #1816
2024-09-30 19:59:34 +02:00
ejurgensen
b9fa790f50 Revert "[daap] Fix for Apple Music/iTunes not working on Airplay host"
This reverts commit 73864e82cd267da65b7245430bd1a2060d8bec81.
2024-09-30 16:13:31 +02:00
ejurgensen
73864e82cd [daap] Fix for Apple Music/iTunes not working on Airplay host
OwnTone selects ALAC for daap streaming because it thinks the client is an Airplay speaker.

Regression in 28.10 coming from PR #1698.

Fixes #1816
2024-09-29 22:22:26 +02:00
ejurgensen
ba4b2c8ddd [httpd/daap] Improve logging of speaker profile selection 2024-09-28 20:31:13 +02:00
ejurgensen
17ef308489 [mpd] Fix outputs enable/disable/toggle not working in v28.10
Also simplify code by using new speaker index from player and remove some code
duplication.

Regression from PR #1779. Fixes #1814.
2024-09-25 19:31:16 +02:00
ejurgensen
a983302b03 [player] Add a index attribute to struct player_speaker_info + a getter 2024-09-25 19:28:53 +02:00
github-actions[bot]
dc41f0d84c [web] Rebuild web interface 2024-09-24 18:40:06 +00:00
Alain Nussbaumer
164d6ac9b2 [web] Update to newer versions of libraries 2024-09-24 20:39:32 +02:00
Alain Nussbaumer
66c2873d32 [web] Migrate to Bulma 1.0.2 2024-09-24 20:31:51 +02:00
Fabian Groffen
75222cafd3 [mpd] outputs: drop invalid outputsvolume key
This key confuses some clients, and it isn't emitted by MPD, nor
documented to exist:
9ff8e02e54/src/output/Print.cxx

It was added in bdb2c7493, but without explanation why it was added to
outputs command.

Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2024-09-16 21:21:46 +02:00
ejurgensen
94ce56d7b1 [mpd] Add Bison/Flex parser for complex mpd commands + misc
Some of the misc:
* Initialize query_params from the start of each function
* Reduce code duplication by consolidating the handler's integer conversion
* Go back to classic int return types for handlers
* Change list grouping to respond like mpd
* Sanitize all user string output
2024-09-16 21:17:32 +02:00
ejurgensen
1e485f7d28 [mpd] Make protocol version a #define 2024-09-16 21:17:32 +02:00
ejurgensen
6b55ee2890 [db] free_query_params should also free qp->group
Plus honor qp->order in queue_enum_start() if set by caller
2024-09-16 21:17:32 +02:00
ejurgensen
783f918c5e [mpd] Refactor commit 9962c743 and other stuff in mpd.c 2024-09-16 21:17:32 +02:00
gd
4b8ecfe18d [mpd] Fix: allow password command when not authenticated 2024-09-16 21:17:32 +02:00
gd
e1628ff1a9 [mpd,db] MPD protocol fixes to handling of idle/noidle command and command list.
Command handling:
1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received
command line.
2. mpd_process_line() handles idle state and command list state and delegates
to mpd_process_command() to handle each command line.
If the command was successful it sends OK to the client according the the
command list state.
Error responses are sent by mpd_process_command().
3. mpd_process_command() parses the args and delegates to the individual
command handler.

mpd_input_filter:
1. Removed handling of command lists. They are handled by mpd_process_line().
2. Return BEV_OK if there's at least one complete line of input even if there's
more data in the input buffer.

Idle/noidle:
1. Changed mpd_command_idle() to never write OK to the output buffer.
Instead it is the responsibility of the caller to decide on the response.

2. Removed mpd_command_noidle() instead it is handled in mpd_process_line().
If the client is not in idle state noidle is ignored (no response sent)
If the client is in idle state then it changes idle state to false and sends
OK as the response to the idle command.

Command lists:
1. Added command list state to the client context so commands in the list are
buffered and only executed after receiving command_list_end.

Connection state:
1. Added is_closing flag in the client context to ignore messages received
after freeing the events buffer in intent to close the client connection.

Command arguments parsing:
1. Updated COMMAND_ARGV_MAX to 70 to match current MPD.
2. Changed mpd_pars_range_arg to handle open-ended range.

Command pause:
1. pause is ignored in stopped state instead returning error.

Command move:
1. Changed mpd_command_move() to support moving a range.
2. Added db_queue_move_bypos_range() to support moving a range.

Command password:
1. Password authentication flag set in handler mpd_command_password() instead
of in command processor.

Config:
1. Added config value: "max_command_list_size".
   The maximum allowed size of buffered commands in command list.
2024-09-16 21:17:32 +02:00
ejurgensen
e9485d34ae Bump to version 28.10 2024-09-12 17:23:08 +02:00
ejurgensen
1bee7e0d4b ChangeLog for OwnTone 28.10 2024-09-12 17:21:51 +02:00
ejurgensen
3c99e5a35c [mpd] Set protocol version back 0.20.0
Announcing 0.22.4 without supporting the newer protocol's filters breaks Rigelian
2024-09-12 17:15:48 +02:00
Alain Nussbaumer
87ec17c243 [web] Fix top navigation bar on small screens 2024-09-10 21:46:50 +02:00
Alain Nussbaumer
3127da51b1 [web] Update versions of libraries 2024-09-10 21:08:21 +02:00
Alain Nussbaumer
5c19d7d579 [web] Order of attributes 2024-09-10 21:07:02 +02:00
Alain Nussbaumer
c8e4862245 [web] Avoid the scrollable page behind all modal dialogs 2024-09-10 21:05:12 +02:00
Alain Nussbaumer
8f627f1df0 [web] Avoid scrollable page behind the modal dialog 2024-09-09 21:03:59 +02:00
Alain Nussbaumer
c377ae3a64 [web] Change about page 2024-09-09 21:02:00 +02:00
Alain Nussbaumer
3d9cec4ded [web] Change navigation bars 2024-09-09 21:00:35 +02:00
Alain Nussbaumer
e0a2ab159e [web] Replace switch control 2024-09-09 20:55:41 +02:00
Alain Nussbaumer
e12ab3dd08 [web] Remove useless style elements 2024-09-08 22:01:04 +02:00
ejurgensen
2bf0505cca Revert "[docs] Add note to README about Spotify being broken"
This reverts commit e82340247d83b22998ee8264987b22661c6dd87c.
2024-09-06 17:06:14 +02:00
ejurgensen
e82340247d [docs] Add note to README about Spotify being broken
Ref #1803
2024-09-05 21:41:15 +02:00
ejurgensen
40c22e3d2f [mpd] Fix possible stack buffer overflow in argument parser 2024-08-28 20:11:07 +02:00
ejurgensen
ecab3266ce [misc] Fix XML-encoding of strings with XML chars
xmlNewDocRawNode() takes unescaped strings, so it can handle a string with
like "Sweet & Low"

Closes #1798
2024-08-23 21:12:25 +02:00
github-actions[bot]
ab63f8f834 [web] Rebuild web interface 2024-08-22 20:06:59 +00:00
Alain Nussbaumer
f08de1fc32 [web] Remove text "No results." at the end of Spotify album list 2024-08-22 22:06:28 +02:00
Alain Nussbaumer
f54a435d15 [web] Fix opening of Spotify playlists 2024-08-22 22:05:29 +02:00
github-actions[bot]
028adbaa1c [web] Rebuild web interface 2024-08-22 19:40:48 +00:00
Alain Nussbaumer
f3a656d313 [web] Fix for package-lock.json 2024-08-22 21:40:18 +02:00
Alain Nussbaumer
f02038f5e9 [web] Upgrade to new version of axios library 2024-08-22 21:37:08 +02:00
Alain Nussbaumer
12eaa85c74 [web] Remove sass processor warnings 2024-08-22 21:34:45 +02:00
Alain Nussbaumer
ee6f81a618 [web] Format translation file 2024-08-22 21:34:45 +02:00
Alain Nussbaumer
8b586728b6 [web] Change to Pinia store 2024-08-22 21:34:45 +02:00
ejurgensen
ed16cc7928 [scan] Fix playlist scanner ignoring lines starting with non-ascii chars
Resolves #1795
2024-08-20 19:05:36 +02:00
ejurgensen
92495a7fac [httpd] Fix crash if websocket init fails, e.g. port can't be bound
Ref PR #1792
2024-08-14 17:44:22 +02:00
Fabian Groffen
282e227c64 [mpd] bump supported version to 0.22.4
Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2024-08-13 16:10:56 +02:00
Fabian Groffen
a0d7c1a34f [mpd] add binarylimit command (unused still)
Maintain the binary limit, and use the default MPD uses too.  This comes
in handy when we are going to send binary responses such as in PR #1780

Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2024-08-13 16:10:56 +02:00
Fabian Groffen
42ac0528a9 [mpd] update server version to 0.21.0
Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2024-08-12 10:40:36 +02:00
Fabian Groffen
c0331f527e [mpd] implement oneshot for single mode as noop for 0.21 protocol
Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2024-08-12 10:40:36 +02:00
Fabian Groffen
8eae74257d [mpd] add "httpd" output plugin when enable_http_plugin
Return MPD-compatible output of plugin type "httpd" when
enable_http_plugin in mpd section of the config is set.

Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2024-08-10 20:38:41 +02:00
Fabian Groffen
8f3c99ec43 [mpd] add option enable_httpd_plugin
Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2024-08-10 20:38:41 +02:00
Fabian Groffen
5e68381fe4 [mpd] add "plugin" to outputs response
The plugin key is used by some clients to determine whether local
playback is possible via HTTP stream, so mimick it.

Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2024-08-10 20:38:41 +02:00
Fabian Groffen
b2a957cdec [mpd] return outputs by ascending ID
Simply casting the speaker ID from a 64-bits int to an unsigned short
and hoping that there will be no clashes is just optimistic.  Use an
ascending number instead which is what MPD does too.  The MPD server
specifically documents no persistence in these IDs so we can simply
enumerate the speakers to meet the requirements.

Signed-off-by: Fabian Groffen <grobian@gentoo.org>
2024-08-10 20:38:41 +02:00
ejurgensen
d672332750 [xcode] Fix last seconds being skipped when custom filters enabled
Seems EOF was not marked towards the ffmpeg filtergraph via a NULL frame to
av_buffersrc_add_frame(). This meant that the end of the track would get
stuck in the filters.

Fixes #1787
2024-08-09 17:42:58 +02:00
ejurgensen
297de1409a [xcode] Fix comment 2024-08-09 16:03:11 +02:00
ejurgensen
dab9089f8e [rcp] Fix for RCP devices not getting removed 2024-08-08 00:22:41 +02:00
github-actions[bot]
96cd401852 [web] Rebuild web interface 2024-08-05 19:23:12 +00:00
Alain Nussbaumer
56cea74f38 [i18n] Add traditional Chinese 2024-08-05 21:22:42 +02:00
github-actions[bot]
cdd81af979 [web] Rebuild web interface 2024-08-05 19:19:17 +00:00
Alain Nussbaumer
2f21e91610
Merge pull request #1789 from dwong33/master
Amendement of translation and introduction of Traditional Chinese translation
2024-08-05 21:18:48 +02:00
Alain Nussbaumer
cabd8a3497
Missing traditional Chinese translation 2024-08-05 21:18:09 +02:00
Alain Nussbaumer
def9c7c513
Update zh-TW.json
Using hyphens instead of underscores.
2024-08-05 21:15:43 +02:00
Dwong33
5924e001df
Update index.js to add Traditional Chinese translation 2024-08-05 10:38:38 -04:00
Dwong33
1129e65f61
Introduction to Traditional Chinese translation
Introduction to traditional Chinese, it is a variation of Chinese mainly used in Taiwan, Hong Kong and overseas Chinese communities.
2024-08-05 10:32:42 -04:00
Dwong33
74f82c396e
Update and rename zh.json to zh-CN.json
Introduction of Traditional Chinese, so change the name to to zh-CN. Also a correction to a translation.
2024-08-05 10:28:55 -04:00
ejurgensen
cab0204d29 [httpd] Fix for password-based auth for Apple Music
Adds handling of the username from Apple Music that contain colon. Example:
iTunes_Music/1.4.5 (Macintosh; OS X 14.5) AppleWebKit/618.2.12.11.6 build/36 (dt:1):password

Closes #1778
2024-08-02 16:34:57 +02:00
ejurgensen
59bba5e261 [scan] Fix missing rescan if a file is modified quickly multiple times
If db_timestamp == file_mtime we didn't rescan, just pinged the file in the db

Fixes #1782
2024-07-26 16:04:01 +02:00
ejurgensen
90a79090ea [docs] Add documentation of --mdns-no-xxx options to command line help
Closes #1781
2024-07-25 22:53:56 +02:00
Alain Nussbaumer
53b06e26e3 [doc] Fix notes style 2024-07-20 23:05:35 +02:00
Alain Nussbaumer
9fe89a028e [doc] Add note for playback on iOS devices 2024-07-20 23:02:06 +02:00
ejurgensen
13131f43ef [httpd] Workaround evhttp_connection_get_addr() issue in libevent 2.1.6 and 8
Fixes #1775
2024-07-05 16:02:06 +02:00
ejurgensen
be1bacf278 [cfg] Add "none" as "trusted_networks" option 2024-07-05 16:00:47 +02:00
ejurgensen
bcdd3b2f65 [cache] Silence false gcc maybe-uninitialized warning 2024-06-25 23:39:06 +02:00
ejurgensen
f87b65f086 [misc] Add missing include of stdlib
Thanks @LordMyschkin. Closes #1772.
2024-06-25 23:38:24 +02:00
github-actions[bot]
d9818434a0 [web] Rebuild web interface 2024-06-25 18:55:16 +00:00
Alain Nussbaumer
fc0c6cca77 [web] Update to newer versions of library for security reason 2024-06-25 20:54:46 +02:00
ejurgensen
5542492d33 [xcode] Fix calculation of estimated wav sizes (causing M1001 crash)
Fix regression from commit 3ee9204 where wav size calculation was changed. The
new method caused Soundbridge M1001 to reboot, and the likely cause seems to be
that the sizes in the wav header may not be multiples of channels x
bytes_per_sample, so typically 4.

Resolves #1770
2024-06-24 23:55:32 +02:00
ejurgensen
7ddb4e9bbb [httpd] Fix startup crash after merge commit 4cbce79
With debug level loggint, the crash would be preceeded with "Checking if client
'(null)' is a speaker"

Resolves #1771
2024-06-22 17:34:04 +02:00
ejurgensen
d6d46de399 [misc] Fixup missing "== 0" in commit c30f44f 2024-06-17 23:35:03 +02:00
ejurgensen
82a0e77eb6 [config] Keep "cache_path" option but mark as deprecated
So OwnTone will still start if user has previously configured "cache_path"
2024-06-17 23:04:20 +02:00
ejurgensen
4cbce79a0f Merge branch 'rsp_daap_format3' 2024-06-17 22:37:52 +02:00
ejurgensen
7dd34792ea [cache] Multitreaded header encoding 2024-06-17 22:25:20 +02:00
ejurgensen
088c393dd6 [httpd/cache] Misc fixing up 2024-06-17 22:25:20 +02:00
ejurgensen
2d9200fcdf [cache] Try to fix cache thread blocking the rest of the server 2024-06-17 22:25:20 +02:00
ejurgensen
ff2d0b4ab1 [httpd] Check speaker settings to determine DAAP/RSP stream format 2024-06-17 22:25:20 +02:00
ejurgensen
62b42ce354 [misc/player] Introduce output ability to announce supported formats
Also introduce default output format and selected device format, should the
user want another format.

As part of this, change enum player_format in player.h to enum media_format in
misc.h so that it is akin to struct media_quality.

Modify json API to support this.
2024-06-17 22:25:20 +02:00
ejurgensen
9f719ca155 [player/jsonapi/db] Add interface to get and set an output format 2024-06-17 22:25:20 +02:00
ejurgensen
c079df5da7 [cache/config] Refactor cache so daap/artwork/xcode is cached in separate db's
Also change config so that the user can just configure a data directory instead
of complete path to each database.
2024-06-17 22:25:20 +02:00
ejurgensen
2efad1466f [cache] Add support for storing MP4 headers 2024-06-17 22:05:07 +02:00
ejurgensen
4a08644806 [xcode] Change the signature of transcode_setup/transcode_en/decode_setup()
Should make it easier to add/remove parameters without changing all calls to the
functions throughout the code.

Also adds an interface through which to call make_mp4_header().
2024-06-17 22:05:07 +02:00
ejurgensen
9dbec4b99e [misc] Move endianess utils from rtp_common.h to misc.h
So that transcode.c can also use them.
2024-06-17 22:05:07 +02:00
ejurgensen
725419d4ac [xcode] Do without _GNU_SOURCE, MacOS doesn't like it 2024-06-17 22:05:07 +02:00
ejurgensen
aed74fbb8a [xcode] Support for ALAC encoded RSP/DAAP streaming
Also changes WAV encoding to use source quality instead of fixed 44100/16/2.
Sets the default to WAV, since it has the best quality, and doesn't have the
delay that creating an MP4 container for ALAC currently has (TODO prepare
the headers to avoid the delay).

Ref issue #1182 and #1665
2024-06-17 22:05:07 +02:00
ejurgensen
c30f44fd01 [misc] New net_peer_address_is_trusted with cfg set default to "lan"
New default for "trusted_networks" = "lan". This will check peer addresses
against the addresses/netmasks of the interfaces to establish whether the peer
is local.

Fixes #1754
2024-06-16 01:17:11 +02:00
ejurgensen
bf73e51262
Merge pull request #1769 from owntone/fix_rename_pl1
[scan] Fix bug where playlist name isn't updated when m3u/pls file is…
2024-06-12 16:04:34 +02:00
ejurgensen
2219e3ce75 [scan] Fix bug where playlist name isn't updated when m3u/pls file is renamed
Closes #1758
2024-06-12 13:12:37 +02:00
ejurgensen
3af04afa61 [dmap] Change date type to int64, fix for "Integer value too large"
uint32 won't work for dates before the Unix epoch, and int32 won't work after
2038, so let's see if clients can handle int64.

Resolves #1742
2024-06-09 22:20:33 +02:00
ejurgensen
a5a991e1fa
Merge pull request #1767 from hacketiwack/master
[ci] Streamline macOS pipelines
2024-06-09 22:09:24 +02:00
Alain Nussbaumer
325a3609a0 [ci] Streamline macOS pipelines 2024-06-09 11:10:22 +02:00
ejurgensen
2d59762520 [gh-actions] Correct spelling of macOS 2024-06-05 22:47:01 +02:00
ejurgensen
390c335562 [gh-actions] Simplify the macOS build
Credit @hackitwack
2024-06-04 23:07:44 +02:00
ejurgensen
e7459e0576 [mdns] Fix OwnTone not updating speaker addresses on ip change
The fix is to use proto instead of addr->proto when creating the record browser
in browse_resolve_callback(). The rest of the commit is just minor
beautification.

Resolves #1760. The issue seems to be a regression from commit
e7a73551ab59d06256602ebce6961ea8811e7584.
2024-06-04 17:11:45 +02:00
ejurgensen
9439bcc60c [gh-actions] Fix building for macos-latest (Mac OS 14 Sonoma) 2024-06-04 16:58:43 +02:00
ejurgensen
9f0fa7c45c [gh-actions] Add a manual workflow for Mac OS 12 2024-06-04 16:55:56 +02:00
ejurgensen
f8d42a2fef
Merge pull request #1761 from owntone/mxml_to_libmxml_1 2024-06-01 01:54:51 +02:00
ejurgensen
f6ee669b80 [docs] Change mxml to libxml2 2024-05-31 23:47:46 +02:00
ejurgensen
b6ee09925b [gh-actions] Change dependencies from mxml to libxml2 2024-05-31 23:47:46 +02:00
ejurgensen
75b8f06e25 [misc] Replace mxml with libxml2
mxml 4 is binary and source incompatible with 3, and there is no easy way to
stay compatible with both. Not great for a library. So replace with libxml2,
hopefully that is more stable. Also means we can get rid of all the mxml hacks
2024-05-31 23:47:46 +02:00
ejurgensen
75fe3f100a [gh-actions] Switch from macos-latest (14) to macos-12
Seems there are a bunch of problems with linking to the Homebrew installed
libraries on MacOS 14, so this is a temporary fix to get the GH Action somewhat
working again.
2024-05-31 23:39:07 +02:00
ejurgensen
2bb97c8e0a [cfg] Change ipv6 default to disabled
There is some issue with OwnTone's Airplay ipv6 implementation so disable ipv6
in the config until it is fixed.

Ref issue #1752
2024-05-20 22:21:00 +02:00
github-actions[bot]
d951957730 [web] Rebuild web interface 2024-05-19 20:43:38 +00:00
Alain Nussbaumer
408057a45d [web] Merge branch 'linting' 2024-05-19 22:42:45 +02:00
Alain Nussbaumer
84d728d46e [doc] Make configuration page more readable
This page should probably be split.
2024-04-27 22:38:08 +02:00
Alain Nussbaumer
17d1ceef07 [doc] Add configuration documentation in the menu
Moreover, the entries in the menu now match the title of the pages.
2024-04-27 21:53:01 +02:00
Alain Nussbaumer
9319118190 [web] Simplify variable naming 2024-04-27 21:34:16 +02:00
Alain Nussbaumer
2d7776619f [web] Remove unused variables 2024-04-27 21:18:28 +02:00
Alain Nussbaumer
2c58351bec [web] Simplify variable naming 2024-04-27 21:14:31 +02:00
Alain Nussbaumer
7edce91474 [web] Make hover style of buttons identical everywhere 2024-04-27 21:13:13 +02:00
Alain Nussbaumer
4ae2903b4d [web] Simplify variable naming 2024-04-27 20:36:04 +02:00
Alain Nussbaumer
c255b3a108 [web] Simplify variable naming 2024-04-27 07:00:08 +02:00
Alain Nussbaumer
fc5563fe9d [web] Remove negated conditions 2024-04-27 06:59:47 +02:00
Alain Nussbaumer
7d59abee4f [web] Fix URL for axios 2024-04-27 06:49:51 +02:00
Alain Nussbaumer
4a1b4575fe [web] Fix variable name 2024-04-27 06:46:02 +02:00
Alain Nussbaumer
5db55f66c1 [web] Enforce single line HTML elements 2024-04-26 23:26:56 +02:00
Alain Nussbaumer
de847a6711 [web] Reorder methods and attributes 2024-04-26 23:09:45 +02:00
Alain Nussbaumer
ea947df50a [web] Use named capture groups 2024-04-26 22:54:32 +02:00
Alain Nussbaumer
7826b36634 [web] Fix shadowed variables 2024-04-26 21:48:07 +02:00
Alain Nussbaumer
8a303f340b [web] Return the list itself when group operation is done 2024-04-26 21:06:51 +02:00
Alain Nussbaumer
d246c42a99 [web] Lint too short variable names 2024-04-26 18:24:34 +02:00
Alain Nussbaumer
8fe6cba6ef [web] Rename confusing variable name option 2024-04-26 18:15:35 +02:00
Alain Nussbaumer
0401fb9616 [web] Simplify naming to avoid confusion 2024-04-26 18:10:23 +02:00
Alain Nussbaumer
73040780b9 [web] Remove unnecessary word option 2024-04-25 22:36:04 +02:00
Alain Nussbaumer
30fc35097c [web] Avoid useless assignment 2024-04-25 21:33:38 +02:00
Alain Nussbaumer
938458b56e [web] Remove unused arguments 2024-04-25 21:33:01 +02:00
github-actions[bot]
1e73ba4754 [web] Rebuild web interface 2024-04-23 20:04:32 +00:00
Alain Nussbaumer
b20bdda8e9 [web] Lint source code 2024-04-23 22:02:18 +02:00
Alain Nussbaumer
7d7d38b946 [web] Fix untranslated button 2024-04-23 21:44:35 +02:00
Alain Nussbaumer
4268f41a51 [web] Fix bug preventing the removal of a podcast 2024-04-23 21:14:37 +02:00
Alain Nussbaumer
bab6146345 [web] Lint source code 2024-04-23 20:52:57 +02:00
Alain Nussbaumer
978e344ce2 [web] Lint source code 2024-04-23 20:33:42 +02:00
Alain Nussbaumer
f156bb357a [web] Lint source code 2024-04-23 20:27:50 +02:00
Alain Nussbaumer
3f3ab829c0 [web] Lint source code 2024-04-23 20:26:08 +02:00
Alain Nussbaumer
195135b1b6 [web] Lint source code 2024-04-23 20:16:11 +02:00
Alain Nussbaumer
4c70105b5e [web] Simplify naming of component property 2024-04-23 20:10:59 +02:00
github-actions[bot]
73abc84979 [web] Rebuild web interface 2024-04-23 15:44:23 +00:00
Alain Nussbaumer
d4826695e3 [web] Fix links not being correctly highlighted in dark mode 2024-04-23 17:43:45 +02:00
github-actions[bot]
715e9d32eb [web] Rebuild web interface 2024-04-23 15:37:26 +00:00
Alain Nussbaumer
25e005ff32 [web] Add breadcrumb to navigate through the folders 2024-04-23 17:36:48 +02:00
ejurgensen
263a197da4 Revert "[gh-actions] Use stock Homebrew sqlite"
This reverts commit 6a93172cb991c6b9a622a8424fc8898eab5bc818.
2024-04-23 14:46:53 +02:00
Alain Nussbaumer
52a915c8a0 [doc] Add configuration documentation 2024-04-22 17:32:53 +02:00
Alain Nussbaumer
67e67c8db9 [doc] Fix capitalisation of macOS 2024-04-22 17:32:26 +02:00
github-actions[bot]
0873c6cb65 [web] Rebuild web interface 2024-04-21 18:09:35 +00:00
Alain Nussbaumer
1ef62ac3a6 [web] Fix error in GroupedList provoked by the linting 2024-04-21 20:09:00 +02:00
github-actions[bot]
06f658e1c4 [web] Rebuild web interface 2024-04-21 16:21:13 +00:00
Alain Nussbaumer
a2000c0bc7 [web] Lint source code 2024-04-21 18:20:40 +02:00
Alain Nussbaumer
c3d5c6eab9 [web] Lint source code 2024-04-21 17:59:21 +02:00
github-actions[bot]
0d11f732e1 [web] Rebuild web interface 2024-04-21 15:53:56 +00:00
Alain Nussbaumer
d6391621a0 [web] Upgrade to eslint 9.1.0 2024-04-21 17:48:14 +02:00
Alain Nussbaumer
b8373a4ee0 [web] Lint source code 2024-04-21 17:44:55 +02:00
Alain Nussbaumer
2fda829ac4 [web] Remove unused variable 2024-04-21 16:14:55 +02:00
Alain Nussbaumer
5115e04664 [web] Lint source code 2024-04-21 13:17:22 +02:00
Alain Nussbaumer
369afe11e3 [web] Remove unused variable 2024-04-21 12:58:15 +02:00
Alain Nussbaumer
9690bc2447 [web] Format source code 2024-04-21 12:55:49 +02:00
github-actions[bot]
acf8805dac [web] Rebuild web interface 2024-04-20 21:26:29 +00:00
Alain Nussbaumer
58fbcd7e7a [web] Disable Save button when no playlist name is provided 2024-04-20 23:25:58 +02:00
Alain Nussbaumer
ae973f312a [web] Remove extraneous space 2024-04-20 23:24:43 +02:00
Alain Nussbaumer
185e09c118 [web] Revert back to older version of eslint until configuration is adapted 2024-04-20 23:00:15 +02:00
github-actions[bot]
595c91d5d6 [web] Rebuild web interface 2024-04-20 20:36:33 +00:00
Alain Nussbaumer
465232f8b9 [web] Upgrade to newer versions of libraries 2024-04-20 22:35:58 +02:00
Alain Nussbaumer
13ff8fdb8e [web] Fix color of delete tag button for the dark mode 2024-04-20 22:35:58 +02:00
Alain Nussbaumer
5ce78d041d [web] Remove blanks in the search query before launching a search 2024-04-20 22:35:58 +02:00
ejurgensen
6a93172cb9
[gh-actions] Use stock Homebrew sqlite
GH action's Homebrew should install has sqlite 3.43.0_1+, which has unlock-notify
2024-04-19 08:50:13 +02:00
ejurgensen
f00aae6c6c
[gh-actions] Attempt fix for macOS workflow broken by mxml 4 2024-04-19 08:40:51 +02:00
github-actions[bot]
16b9de01c7 [web] Rebuild web interface 2024-04-15 20:51:26 +00:00
Alain Nussbaumer
1ccc97d824 [web] Present a numeric keypad for integer input fields 2024-04-15 22:50:48 +02:00
Alain Nussbaumer
a2dd2251c9 [web] Allow only numerical values in the integer input field 2024-04-15 22:50:48 +02:00
ejurgensen
72454de4ef [jsonapi] Fix boolean value assigned to pointer (from cppcheck) 2024-04-10 22:41:08 +02:00
ejurgensen
677aceccb6 [cast] Add missing include 2024-04-10 22:40:52 +02:00
ejurgensen
60872e0a5a
Merge pull request #1749 from hacketiwack/master
[workflow] Upgrade GitHub actions to newer versions
2024-04-10 22:01:26 +02:00
Alain Nussbaumer
c1842e383a [doc] Fix links in the documentation 2024-04-10 14:51:55 +02:00
Alain Nussbaumer
867ab0e80a [docs] Fix links in the documentation 2024-04-10 14:40:27 +02:00
Alain Nussbaumer
59a734b04c [workflow] Upgrade GitHub actions to newer versions 2024-04-10 13:39:09 +02:00
github-actions[bot]
183f6f8ed9 [web] Rebuild web interface 2024-04-09 16:52:02 +00:00
Alain Nussbaumer
ff9537514a [web] Fix a bug preventing the files page to not load when refreshing page 2024-04-09 18:51:21 +02:00
github-actions[bot]
60f14adb47 [web] Rebuild web interface 2024-04-09 13:42:12 +00:00
Alain Nussbaumer
5e39828966 [web] Update to newer versions of libraries 2024-04-09 15:41:38 +02:00
Alain Nussbaumer
0362896bfb [web] Add the possibility to remove past search queries 2024-04-09 15:41:38 +02:00
Alain Nussbaumer
e5e7702fc5 [web] Streamline search pages 2024-04-09 15:41:38 +02:00
ejurgensen
c96c3966f4 [gh-actions] [gh-actions] Try to fix MacOS workflow (use mxml 3) mk3 2024-04-06 23:18:24 +02:00
ejurgensen
aaf349bbcc [gh-actions] [gh-actions] Try to fix MacOS workflow (use mxml 3) mk2 2024-04-06 23:06:24 +02:00
ejurgensen
cd5937bbb7 [gh-actions] Try to fix MacOS workflow (use mxml 3) 2024-04-06 22:13:35 +02:00
ejurgensen
1c17231b9e [spotify] Fix some logging inaccuracies 2024-04-05 23:45:08 +02:00
ejurgensen
a8342dc513 [xcode] Fix for ffmpeg 7.0 that wants const in avio_alloc_context's signature
Closes #1743
2024-04-05 22:44:46 +02:00
ejurgensen
945bde7c66 [db] Fix memleak if db backup is enabled but fails
Closes issue #1741
2024-04-03 22:04:39 +02:00
github-actions[bot]
1c26681a65 [web] Rebuild web interface 2024-04-03 15:49:52 +00:00
Alain Nussbaumer
31661edc03 [web] Fix the search page source when clicking the search menu 2024-04-03 17:46:33 +02:00
Alain Nussbaumer
4946c0e43c [web] Add a catch all redirection if a non-existing link is entered 2024-04-03 17:14:56 +02:00
Alain Nussbaumer
81d9b1723f [web] Lint source code 2024-04-03 16:39:48 +02:00
github-actions[bot]
089df85c1d [web] Rebuild web interface 2024-04-01 18:59:58 +00:00
Alain Nussbaumer
839e475c3e [web] Fix a bug preventing the "featured playlists" and "new releases" pages to work after a page refresh 2024-04-01 20:42:05 +02:00
Alain Nussbaumer
72b30aabf9 [web] Fix a bug preventing playlist page to open sub folders 2024-04-01 20:40:08 +02:00
github-actions[bot]
40c423ee3c [web] Rebuild web interface 2024-04-01 14:41:51 +00:00
Alain Nussbaumer
d49074eeae [web] Fix folder page not reloading data 2024-04-01 16:32:47 +02:00
Alain Nussbaumer
be931f4173 [web] Lint source code 2024-04-01 15:13:42 +02:00
Alain Nussbaumer
5640c33a67 [web] Remove duplicate loading of data before route update 2024-03-31 23:55:19 +02:00
github-actions[bot]
285270f598 [web] Rebuild web interface 2024-03-31 19:53:24 +00:00
Alain Nussbaumer
4b52df676a [web] Update axios library version 2024-03-31 21:52:49 +02:00
Alain Nussbaumer
7b41980ace [web] Lint source code 2024-03-31 21:52:49 +02:00
Alain Nussbaumer
cbedb4d38c [web] Fix count of albums and tracks in genre pages 2024-03-31 21:52:49 +02:00
Alain Nussbaumer
2451ac608f [web] Update libraries to their newer versions 2024-03-31 21:52:49 +02:00
Alain Nussbaumer
7be1989cd4 [web] Display only icons in tabs when on mobile
No need to scroll horizontally to switch tabs on the music and audiobook pages anymore.
2024-03-31 21:52:49 +02:00
ejurgensen
3e7e03b4c1 [jsonapi] Support /api/search?type=genre like type=composers is supported
Ref. #1735
2024-03-30 22:24:53 +01:00
github-actions[bot]
39f5df8ade [web] Rebuild web interface 2024-03-29 14:32:43 +00:00
Alain Nussbaumer
0a0568c2f5 [web] Unused API methods removed 2024-03-29 15:32:12 +01:00
Alain Nussbaumer
6577004536 [docs] Clean up the docs 2024-03-29 02:54:34 +01:00
Alain Nussbaumer
ad2d0e0bba [docs] Fix spelling mistakes 2024-03-29 02:19:51 +01:00
github-actions[bot]
eecd276aa3 [web] Rebuild web interface 2024-03-28 14:46:13 +00:00
Alain Nussbaumer
06a23ea29a [web] Check validity of URL 2024-03-28 15:45:39 +01:00
Alain Nussbaumer
d6f08a2d70
Merge pull request #1736 from johtani/update-search-term-doc
[docs] Fix the result types in "Search by search term"

Indeed, that's correct. Thanks for the catch.
2024-03-26 15:41:21 +01:00
github-actions[bot]
218d2ad143 [web] Rebuild web interface 2024-03-26 14:24:02 +00:00
Alain Nussbaumer
99caa615fc [web] Lint source code 2024-03-26 15:22:58 +01:00
Alain Nussbaumer
a449e7aeb3 [web] Fix progress not being displayed on audiobook album page 2024-03-26 15:00:49 +01:00
Alain Nussbaumer
94f331cf09 [web] Lint source code 2024-03-26 15:00:17 +01:00
Jun Ohtani
9869448e17
Update json-api.md
Fix the result types in "Search by search term"
2024-03-26 22:25:24 +09:00
Alain Nussbaumer
57207c1ff4 [web] Lint source code 2024-03-26 12:04:04 +01:00
Alain Nussbaumer
f2c3e8ff50 [web] Lint source code 2024-03-26 11:13:10 +01:00
Alain Nussbaumer
feaa14b76a [web] Streamline names of translation keys 2024-03-26 09:20:48 +01:00
Alain Nussbaumer
545b1d8dee [web] Add missing translation 2024-03-26 09:12:07 +01:00
Alain Nussbaumer
a1960233bf [web] Update to newer versions of libraries 2024-03-26 03:38:40 +01:00
Alain Nussbaumer
24da82f42b [web] Fix variable name 2024-03-26 03:35:22 +01:00
Alain Nussbaumer
2b54de424b [web] Use template strings 2024-03-26 03:34:50 +01:00
Alain Nussbaumer
6a0081cf71 [web] Streamline naming of elements 2024-03-26 03:13:17 +01:00
Alain Nussbaumer
b78bba94ef [web] Fix French spelling mistake 2024-03-26 01:34:06 +01:00
Alain Nussbaumer
0086dde94b [web] Streamline method names 2024-03-26 01:29:05 +01:00
Alain Nussbaumer
3ceb76b016 [web] Lint source code 2024-03-26 01:17:07 +01:00
Alain Nussbaumer
ed5f2028a1 [web] Streamline names of Spotify components to match their library counterpart 2024-03-26 00:59:15 +01:00
Alain Nussbaumer
6fd4db14fb [web] Streamline Spotify pages to match library pages 2024-03-25 21:54:01 +01:00
Alain Nussbaumer
8b1c4decf5 [web] Remove unused template 2024-03-25 18:12:14 +01:00
Alain Nussbaumer
dc3785888b [web] Remove redundancy in the displayed texts 2024-03-25 18:11:09 +01:00
Alain Nussbaumer
36842dfc04 [web] Refactor library search page 2024-03-25 18:03:52 +01:00
Alain Nussbaumer
17c6454afa [web] Align naming in search pages 2024-03-25 18:03:31 +01:00
Alain Nussbaumer
a6b2f93f41 [web] Refactor Spotify search page 2024-03-25 17:30:59 +01:00
Alain Nussbaumer
7adde0340e [web] Set default values for uri and position to play a track 2024-03-25 15:25:00 +01:00
Alain Nussbaumer
c312b2fdfe [web] Generalise search_next function to all types 2024-03-25 14:49:53 +01:00
Alain Nussbaumer
0f3f8d5a36 [web] Refactor the Spotify track item page 2024-03-25 14:00:42 +01:00
Alain Nussbaumer
aa5ae7993a [web] Lint source code 2024-03-24 21:44:30 +01:00
Alain Nussbaumer
581544207b [web] Lint source code 2024-03-24 21:41:58 +01:00
Alain Nussbaumer
0657535d04 [web] Refactor the Spotify artist item page 2024-03-24 20:41:21 +01:00
Alain Nussbaumer
e1b6f9eb2b [web] Lint source code 2024-03-24 20:36:19 +01:00
Alain Nussbaumer
ee48395f1b [web] Refactor the Spotify playlist item page 2024-03-24 20:26:18 +01:00
Alain Nussbaumer
cee1513966 [web] Fix artist name not being displayed 2024-03-24 19:56:38 +01:00
Alain Nussbaumer
fc192c5dfc [web] Refactor the Spotify album item page 2024-03-24 19:29:08 +01:00
Alain Nussbaumer
2b57f1124c [web] Refactor the Spotify album page 2024-03-24 18:39:15 +01:00
Alain Nussbaumer
9705c8cd57 [web] Refactor the Spotify album page 2024-03-24 18:05:29 +01:00
Alain Nussbaumer
c89449e8fd [web] Lint source code 2024-03-24 11:02:46 +01:00
Alain Nussbaumer
e244b82082 [web] Streamline name of component properties to prepare for refactoring 2024-03-24 11:02:11 +01:00
Alain Nussbaumer
439867b95b [web] Lint source code 2024-03-24 11:01:06 +01:00
Alain Nussbaumer
979f0c2a33 [web] Fix for composers not being displayed in the search results when switching from a Spotify to library search 2024-03-24 10:02:18 +01:00
Alain Nussbaumer
1d426f78a6 [web] Streamline name of component properties to prepare for refactoring 2024-03-23 23:58:07 +01:00
Alain Nussbaumer
30aee058bf [web] Streamline name of component properties to prepare for refactoring 2024-03-23 23:46:47 +01:00
Alain Nussbaumer
33d28b085f [web] Lint source code and rename some audio methods 2024-03-23 02:06:30 +01:00
Alain Nussbaumer
152891f6cd [web] Lint source code 2024-03-23 01:53:25 +01:00
ejurgensen
9b12618b93 [scan] Fix date_released being set to 0 on platforms with musl
%F for ISO date format is only glibc. May affect other date parsing.

Closes #1730
2024-03-21 21:04:21 +01:00
github-actions[bot]
b1a3941226 [web] Rebuild web interface 2024-03-21 20:03:58 +00:00
Alain Nussbaumer
604b1d3fdf [web] Lint the source code 2024-03-21 21:01:13 +01:00
Alain Nussbaumer
a34e14f483 [web] Fix the error when the library search only contains the keyword query:
The check for a valid query is not yet implemented though.
2024-03-21 20:57:46 +01:00
Alain Nussbaumer
db4e145080 [web] Remove unused component property 2024-03-21 20:08:31 +01:00
github-actions[bot]
c8f80a4d78 [web] Rebuild web interface 2024-03-20 21:02:02 +00:00
Alain Nussbaumer
5c8639aeef [web] Remove useless component parameter 2024-03-20 22:01:17 +01:00
github-actions[bot]
91b0c3a643 [web] Rebuild web interface 2024-03-20 20:40:31 +00:00
Alain Nussbaumer
c055952bcf [web] Update to newer version of libraries 2024-03-20 21:39:47 +01:00
Alain Nussbaumer
ec07729424 [web] Fix error when search queries contain banks or start with blanks. 2024-03-20 21:39:47 +01:00
ejurgensen
bf8e433a0e
[scan] Fix error in debug function 2024-03-20 09:27:30 +01:00
github-actions[bot]
7d52c14dea [web] Rebuild web interface 2024-03-19 16:44:51 +00:00
Alain Nussbaumer
7c3fd78329 [web] Match the Spotify and library search pages 2024-03-19 17:44:05 +01:00
github-actions[bot]
cd0ec160d6 [web] Rebuild web interface 2024-03-19 16:19:39 +00:00
Alain Nussbaumer
fcdcb9162d [web] Fix composers not being displayed in the library search 2024-03-19 17:17:30 +01:00
github-actions[bot]
ed564b1861 [web] Rebuild web interface 2024-03-18 21:00:04 +00:00
Alain Nussbaumer
44b2cb0aa5 [web] Update to newer version of libraries 2024-03-18 21:59:26 +01:00
github-actions[bot]
666ffc35ea [web] Rebuild web interface 2024-03-18 20:39:07 +00:00
Alain Nussbaumer
3f8ca8cda3 [web] Rewrite of the GroupedList to accept multiple sort criteria 2024-03-18 21:34:43 +01:00
github-actions[bot]
824a37f0a6 [web] Rebuild web interface 2024-03-13 17:17:41 +00:00
Alain Nussbaumer
5c6546f313 [web] Update versions of libraries 2024-03-13 18:17:06 +01:00
Alain Nussbaumer
46a8051e41 [web] Format source code 2024-03-13 18:15:18 +01:00
Alain Nussbaumer
4ca397a8f2 [web] Streamline HTML code across the pages 2024-03-13 18:14:55 +01:00
github-actions[bot]
7e78984f2b [web] Rebuild web interface 2024-03-13 16:33:11 +00:00
Alain Nussbaumer
601962c8d8 [web] Fix a bug preventing to index buttons to work in the genre pages 2024-03-13 17:32:35 +01:00
Alain Nussbaumer
b59a1b9407 [web] Remove ternary conditional operator 2024-03-12 14:04:05 +01:00
github-actions[bot]
2273b917c7 [web] Rebuild web interface 2024-03-12 12:46:26 +00:00
Alain Nussbaumer
9513097dd0 [web] Fix sorting that is not kept in some situations 2024-03-12 13:45:53 +01:00
Alain Nussbaumer
006093c643 [docs] Make web interface documentation more comprehensible 2024-03-12 01:36:30 +01:00
github-actions[bot]
d35d52c6af [web] Rebuild web interface 2024-03-05 13:22:19 +00:00
Alain Nussbaumer
37b1c834c9 [web] Remove unnecessary HTML elements 2024-03-05 14:21:35 +01:00
Alain Nussbaumer
3f4c6b2cf0 [web] Simplify styling 2024-03-05 13:25:07 +01:00
Alain Nussbaumer
ea450665da [web] Set loading progress bar at the top 2024-03-05 13:17:05 +01:00
github-actions[bot]
dabda617ee [web] Rebuild web interface 2024-03-05 11:17:40 +00:00
Alain Nussbaumer
9ffe5d7df8 [web] Remove unnecessary CSS style 2024-03-05 12:16:55 +01:00
Alain Nussbaumer
2492f51022 [web] Remove unnecessary CSS class 2024-03-05 11:55:08 +01:00
Alain Nussbaumer
10ce7f30a4 [web] Update versions of libraries 2024-03-05 11:38:43 +01:00
Alain Nussbaumer
815e7b45d2 [docs] Fix spelling mistakes and extraneous spaces 2024-03-05 11:23:25 +01:00
Alain Nussbaumer
1e6999587a
[docs] Avoid page to link outside website 2024-03-05 10:09:19 +01:00
Alain Nussbaumer
6f4de54fa3
Merge pull request #1727 from johtani/patch-1
[Docs] Fix smart-playlist link
2024-03-05 10:03:34 +01:00
Alain Nussbaumer
cd458682d4
Update json-api.md 2024-03-05 10:02:41 +01:00
Jun Ohtani
25b6f455e3
[Docs] Fix smart-playlist link
Fix broken link about smart playlist query expression
2024-03-05 17:53:39 +09:00
ejurgensen
c293f72846 [httpd] Fix for musl libc not having sys/queue.c (closes #1726) 2024-03-03 22:41:25 +01:00
github-actions[bot]
68b7ccf4d2 [web] Rebuild web interface 2024-03-02 08:54:16 +00:00
Alain Nussbaumer
91ef635ff5 [web] Streamline source code 2024-03-02 09:53:38 +01:00
Alain Nussbaumer
2319ca7cb8 [web] Add border for some buttons in dark mode 2024-03-02 09:53:38 +01:00
ejurgensen
49de0240b8 [scan] Remove obsolete comments 2024-03-01 23:37:17 +01:00
ejurgensen
4352f54c11 [scan] Use copy_file_range for Linux/FreeBSD file copy
FreeBSD doesn't seem to actually have copyfile.h
2024-03-01 22:59:23 +01:00
github-actions[bot]
d04778fd89 [web] Rebuild web interface 2024-02-29 20:19:40 +00:00
Alain Nussbaumer
74608d8a55 [web] Update to newer versions of libraries 2024-02-29 21:18:04 +01:00
Alain Nussbaumer
aa57cd443f [web] Remove library string-to-color 2024-02-29 21:17:46 +01:00
Alain Nussbaumer
b845453274 [web] Lint source code 2024-02-29 13:16:09 +01:00
Alain Nussbaumer
b352e964d0 [web] Return the value of slider as a number 2024-02-29 13:14:19 +01:00
Alain Nussbaumer
2747289831 [web] Reorganise imports 2024-02-29 11:09:38 +01:00
Alain Nussbaumer
73daaa9cd7 [web] Remove unused method 2024-02-29 10:53:24 +01:00
Alain Nussbaumer
e07a02b027 [web] Remove useless test in beforeRouteUpdate method 2024-02-29 10:52:36 +01:00
Alain Nussbaumer
1d90938598 [docs] Add Docker / Podman package 2024-02-28 16:47:22 +01:00
github-actions[bot]
ec4bb5a5d1 [web] Rebuild web interface 2024-02-28 15:11:13 +00:00
Alain Nussbaumer
f15a3895c5 [web] Update to newer versions of libraries 2024-02-28 16:09:45 +01:00
Alain Nussbaumer
bd9c844284 [web] Enforce more linting rules 2024-02-28 16:09:45 +01:00
Alain Nussbaumer
e89d625f15 [web] Streamline code between pages 2024-02-28 16:09:45 +01:00
Alain Nussbaumer
408ac7e8c2 [web] Fix regression on hero header 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
c2c758d9f4 [web] Reorganise imports 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
564e83fbc4 [web] Format source code 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
9bf58f8966 [web] Reorganise imports 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
60083f04f5 [web] Fix non-required property 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
1bdfd68807 [web] Format source code 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
8aa2b3d5ac [web] Simplify method artwork_url 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
1936ce6621 [web] Remove unused computed property 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
617599ee0c [web] Remove maxwidth and maxheight that is not used 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
84f209b520 [web] Reorder components and imports 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
e7ae478e9b [web] Fix property which is not required 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
0fc2032e4a [web] Remove maxwidth and maxheight attribute that have no effect with Spotify URLs 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
0bd7f8ed08 [docs] Fix minor mistakes 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
051a5e8c6a [web] Remove unused methods 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
127db529ef [web] Remove unused property 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
b24e025b43 [web] Add default value for properties 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
ff8b8a0399 [web] Add types to properties 2024-02-28 16:09:44 +01:00
Alain Nussbaumer
c94b905d72 [web] Rename indexList to indices 2024-02-28 16:09:44 +01:00
github-actions[bot]
062a98b2a8 [web] Rebuild web interface 2024-02-27 12:03:40 +00:00
Alain Nussbaumer
085f7a68b6 [web] Fix color of text in the files page 2024-02-27 13:02:50 +01:00
github-actions[bot]
1fa2380a79 [web] Rebuild web interface 2024-02-26 20:15:15 +00:00
Alain Nussbaumer
9385f20cc0 [web] Add dark mode to UI #1470 2024-02-26 21:14:36 +01:00
Alain Nussbaumer
b2cadaa4d4 [web] Update libraries to newer versions 2024-02-26 21:13:29 +01:00
Alain Nussbaumer
50697200c4 [web] Remove useless CSS style 2024-02-26 20:33:41 +01:00
Alain Nussbaumer
5b46df45ba [web] Streamline CSS styles and remove useless classes 2024-02-26 20:23:30 +01:00
Alain Nussbaumer
8b634ea4ff [web] Change HTML elements to simplify styling 2024-02-26 15:10:48 +01:00
Alain Nussbaumer
5a6474b10f [web] Simplify classes 2024-02-26 14:51:23 +01:00
Alain Nussbaumer
1ad3cc1730 [web] Remove redundant css classes 2024-02-26 14:39:05 +01:00
Alain Nussbaumer
7f13d7ea95 [web] Remove useless title 2024-02-25 20:31:39 +01:00
github-actions[bot]
a5a97fe5d5 [web] Rebuild web interface 2024-02-24 10:55:40 +00:00
Alain Nussbaumer
4d916810b2 [web] Simplify code for maintainability 2024-02-24 11:54:04 +01:00
Alain Nussbaumer
d0e701e140 [web] Format source code 2024-02-24 11:46:24 +01:00
Alain Nussbaumer
fa5b467922 [web] Remove unused getter 2024-02-24 11:15:49 +01:00
Alain Nussbaumer
2501994707 [web] Format source code 2024-02-24 11:12:39 +01:00
github-actions[bot]
49d75de8e3 [web] Rebuild web interface 2024-02-22 18:32:55 +00:00
Alain Nussbaumer
4af4dd74bd [web] Rename grouped list 2024-02-22 19:32:11 +01:00
Alain Nussbaumer
151af295eb [web] Update versions of libraries 2024-02-22 17:26:43 +01:00
Alain Nussbaumer
b6d9b61764 [web] Reorganise audio library 2024-02-22 17:26:12 +01:00
Alain Nussbaumer
579c636d50 [web] Reorganise i18n 2024-02-22 17:25:46 +01:00
Alain Nussbaumer
5c2845784f [web] Prepare for the switch to Pinia 2024-02-21 14:02:47 +01:00
github-actions[bot]
95521c8a48 [web] Rebuild web interface 2024-02-21 11:31:44 +00:00
Alain Nussbaumer
6329798154 [web] Update to newer versions of libraries 2024-02-21 12:30:40 +01:00
Alain Nussbaumer
3cadee1d48 [web] Remove useless getters of the store 2024-02-21 12:29:16 +01:00
Alain Nussbaumer
bb43de465f [web] Replace component with plain HTML component 2024-01-28 20:27:56 +01:00
github-actions[bot]
9eb391be8b [web] Rebuild web interface 2024-01-28 18:02:45 +00:00
Alain Nussbaumer
4bab3a448b [web] Replace hard-coded loading text in Spotify artist and playlist pages with loading icon 2024-01-28 19:02:18 +01:00
github-actions[bot]
4030dfbad7 [web] Rebuild web interface 2024-01-28 17:47:56 +00:00
Alain Nussbaumer
4d475678d3 [web] Replace hard-coded loading text in Spotify search page with loading icon 2024-01-28 18:47:26 +01:00
github-actions[bot]
65e95d79e5 [web] Rebuild web interface 2024-01-28 16:37:50 +00:00
Alain Nussbaumer
1b6db6e370 [web] Add new versions of libraries 2024-01-28 17:37:20 +01:00
whatdoineed2do
2dc448fa30
[scan/library] Media rating sync (#1681)
Automatically read/write ratings to files in the library, if options read_rating/
write_rating are enabled. Also adds a max_rating so the user can set the rating
scale.

Doesn't sync automatic rating updates, because that could lead to whole-playlist
file rewriting.

Closes #1678 

---------

Co-authored-by: whatdoineed2do/Ray <whatdoineed2do@nospam.gmail.com>
Co-authored-by: ejurgensen <espenjurgensen@gmail.com>
2024-01-24 23:30:02 +01:00
github-actions[bot]
9491a3b980 [web] Rebuild web interface 2024-01-23 10:01:07 +00:00
Alain Nussbaumer
e3832a052a [web] Update to newer versions of libraries for security reasons (CVE-2024-23331) 2024-01-23 11:00:38 +01:00
ejurgensen
fbc556ed67 Bump version to 28.9 2024-01-18 18:22:47 +01:00
ejurgensen
de3a00e950 ChangeLog for OwnTone 28.9 2024-01-18 18:20:59 +01:00
ejurgensen
b4e4583e61 [raop] Remove unused function 2024-01-18 18:18:33 +01:00
ejurgensen
74c82141d4 [artwork] Remove errors in log saying "Source 'stream' returned an error..."
Handler for stream doesn't support URL's with "file:", so ignore any such URLs.
2024-01-12 23:04:59 +01:00
ejurgensen
f5c6a1c770 [docs] Update README.md 2024-01-09 17:21:49 +01:00
ejurgensen
b6ad73e1fa [xcode] Go back to wav as default format for RSP/DAAP transcoding
But add "prefer_format" config option for mp3, incl. a check for an encoder.
The config option is currently not added to owntone.conf.in, so is undocumented
until decided what to do about possible alac support.
2024-01-01 23:22:30 +01:00
ejurgensen
2871a03aa8 [spotify] Fixup commit 182255c 2024-01-01 17:34:07 +01:00
ejurgensen
61bae6367d [spotify/rtp] Remove some unused functions
Closes #1705
2024-01-01 16:37:15 +01:00
ejurgensen
182255cac8 [spotify] Fix double free on logout or exit
Fixes #1706
2024-01-01 16:33:12 +01:00
github-actions[bot]
0b46ac53ed [web] Rebuild web interface 2023-12-31 13:16:45 +00:00
Alain Nussbaumer
ac2adac8ab [web] Correct Chinese punctuation 2023-12-31 14:16:17 +01:00
github-actions[bot]
d6f6508638 [web] Rebuild web interface 2023-12-31 12:32:57 +00:00
Alain Nussbaumer
7119d95713 [web] Bring more consistency in the settings page 2023-12-31 13:32:17 +01:00
github-actions[bot]
1e5af30519 [web] Rebuild web interface 2023-12-31 12:26:18 +00:00
Alain Nussbaumer
864f28de9a [web] Use OwnTone colours 2023-12-31 13:25:44 +01:00
Alain Nussbaumer
ec289a8b1d [web] Remove unused translations 2023-12-31 13:24:24 +01:00
Alain Nussbaumer
69f4af5df6 [web] Remove unused API method 2023-12-31 12:56:26 +01:00
Alain Nussbaumer
1c7a4b2a4d [web] Update to newer versions of libraries 2023-12-31 12:54:57 +01:00
ejurgensen
e03120c944 [web] Fix possible invalid read during shutdown
The player may emit a listener event when shutting down, and since websockets
didn't remove it's listener callback it would receive an event despite being
deinitialized. This would lead to an invalid read by lws_cancel_service in
listener_cb().
2023-12-29 17:34:48 +01:00
ejurgensen
c28d108b96 [raop] Fix crash when keys of incorrect length are used for legacy pairing
Happens if the user has paied with Airplay 2, and afterwards activates Airplay 1
for the same device, since the keys in device->auth_keys will then be incorrect
length.

Closes #1703
2023-12-28 13:54:52 +01:00
github-actions[bot]
3fe4c9f289 [web] Rebuild web interface 2023-12-27 16:26:33 +00:00
Alain Nussbaumer
54ad586941 [web] Remove useless i18n configuration 2023-12-27 17:14:10 +01:00
Alain Nussbaumer
c5628adb55 [web] Upgrade libraries 2023-12-27 17:10:09 +01:00
ejurgensen
2fa80b2fd9 [spotify] Remove logging of "No spotify refresh token found"
Logged every time the web UI is used due to call to /api/spotify and during
initscan, but it isn't an error, it just means the user isn't logged in.

Fixes #1701.
2023-12-22 23:29:40 +01:00
ejurgensen
045edf7c55 [httpd] Replace syscall with gettid, syscall deprecated on MacOS 2023-12-19 22:56:58 +01:00
ejurgensen
a7f44dc3e8 [jsonapi] Fix mistaken call to library_media_save
Only library modules should be calling that function
2023-12-19 21:27:31 +01:00
ejurgensen
f430b71645 [jsonapi] Include info about tracks added to queue (closes issue #1564) 2023-12-17 23:42:57 +01:00
ejurgensen
5ea49c94de [library/db/jsonapi] Refactor adding items to queue via JSON API v2 2023-12-17 23:42:57 +01:00
ejurgensen
1ea90b9445 [library/db/jsonapi] Refactor adding items to queue via JSON API
Reduce code duplication and move database specific stuff from httpd_json.c
to db.c.
2023-12-17 23:42:57 +01:00
ejurgensen
cbfce63f4d [db] Use songalbumid's that are distinct for data_kind
Means the same album won't contain both e.g. Spotify and local tracks, they
will instead be shown as two albums.

Ref issue #1650
2023-12-17 21:12:31 +01:00
github-actions[bot]
37c10cbb10 [web] Rebuild web interface 2023-12-17 17:39:59 +00:00
Alain Nussbaumer
9dc3918914 [web] Use newer versions of libraries 2023-12-17 18:38:55 +01:00
Alain Nussbaumer
04feda45c9 [web] Add filter for Spotify content on "Artist Tracks" and "Artist Album" pages. Partially answer #1650 2023-12-17 18:37:35 +01:00
ejurgensen
f657780a42 [scan] Fix issue where albums get split
Regression from PR #1655. This reverts the metadata search to the method used
before the PR, so that search is done in the order set by the metadata maps.

This means the songalbumid is set by the tag with the highest precedence, not
just the first one found.

Closes #1696
2023-12-16 00:11:54 +01:00
Alain Nussbaumer
2d33dea6d3 [web] Use newer versions of libraries 2023-12-15 17:53:42 +01:00
Alain Nussbaumer
955b9658e9 [web] Fix the example command to run the local server instance 2023-12-15 17:53:17 +01:00
Alain Nussbaumer
abbd02e925 [web] Revert back to having one chunk of JavaScript for the web interface 2023-12-15 17:28:33 +01:00
Alain Nussbaumer
a076a6d47f [web] Add the web interface files to the makefile 2023-12-14 22:34:54 +01:00
github-actions[bot]
c5f11e1c14 [web] Rebuild web interface 2023-12-14 20:00:28 +00:00
Alain Nussbaumer
67f716ff43 [web] Reorder properties and methods 2023-12-14 20:47:27 +01:00
Alain Nussbaumer
1b666fe936 [web] Remove the need to set the layer index for the bottom menu 2023-12-14 20:36:47 +01:00
Alain Nussbaumer
1daf625618 [web] Allows the top menu to be over the tabs 2023-12-14 20:30:34 +01:00
Alain Nussbaumer
5b26bc47fa [web] Reorder methods and properties 2023-12-14 10:48:44 +01:00
Alain Nussbaumer
ba3f656b3a [web] Reorder methods 2023-12-14 10:43:22 +01:00
Alain Nussbaumer
e26055cb76 [web] Use newer versions of libraries 2023-12-14 10:42:43 +01:00
Alain Nussbaumer
8085d0344a [web] Remove unused states 2023-12-14 09:32:46 +01:00
Alain Nussbaumer
db6279bc88 [web] Reorder methods of API alphabetically 2023-12-13 20:54:22 +01:00
Alain Nussbaumer
775108f088 [web] Clean code for better maintainability 2023-12-13 20:15:07 +01:00
Alain Nussbaumer
6f6d804e44 [web] Use newer versions of libraries 2023-12-12 21:23:29 +01:00
Alain Nussbaumer
5e04a9d22a [web] Make GroupByList class more maintainable 2023-12-12 21:20:41 +01:00
Alain Nussbaumer
7c9df8cc79 [web] Reorganise store properties alphabetically 2023-12-12 21:17:28 +01:00
Alain Nussbaumer
d4dbd02930 [web] Add default values to the library object 2023-12-12 21:10:00 +01:00
Alain Nussbaumer
012f5d6635 [web] Use of lazy loading routes 2023-12-12 20:58:35 +01:00
Alain Nussbaumer
45b50086b9 [web] Remove unnecessary computed property 2023-12-12 20:29:21 +01:00
Alain Nussbaumer
d50c94a63c [web] Remove unused computed properties 2023-12-12 20:06:34 +01:00
github-actions[bot]
bc120316b3 [web] Rebuild web interface 2023-12-09 21:28:23 +00:00
Alain Nussbaumer
ddf45735e0 [web] Fix for Spotify podcast episodes not treated properly in the modal dialog for tracks 2023-12-09 22:27:48 +01:00
github-actions[bot]
b604f43a00 [web] Rebuild web interface 2023-12-09 21:06:34 +00:00
Alain Nussbaumer
00343cfa91 [web] Reorder methods 2023-12-09 22:02:11 +01:00
Alain Nussbaumer
5d3fa4e087 [web] Fix modal dialog for tracks 2023-12-09 22:00:03 +01:00
Alain Nussbaumer
777d98ce80 [web] Fix modal dialog for queue items
Whenever a field is not existing for an item, the name of the field is not displayed anymore.
2023-12-09 21:35:51 +01:00
Alain Nussbaumer
76282e2031 [web] Bump library versions 2023-12-09 21:07:46 +01:00
Alain Nussbaumer
b98812f64b [web] Fix code formatting 2023-12-09 10:51:57 +01:00
Alain Nussbaumer
8d501f9ef1 [web] Streamline usage of styles 2023-12-09 10:49:15 +01:00
Alain Nussbaumer
8733eb46f1 [web] Remove unnecessary CSS styles 2023-12-09 10:46:30 +01:00
github-actions[bot]
54884c6870 [web] Rebuild web interface 2023-12-08 20:10:47 +00:00
Alain Nussbaumer
b2ee4f3f19 [web] Use default Bulma progress bar styles 2023-12-08 21:09:14 +01:00
Alain Nussbaumer
7fcc0473a8 [web] Order icons alphabetically 2023-12-08 11:55:17 +01:00
github-actions[bot]
8df6f7df45 [web] Rebuild web interface 2023-12-08 09:13:32 +00:00
Alain Nussbaumer
e89c3929cc [web] Add the medium numbers when an album has multiple media #1629 2023-12-08 10:12:03 +01:00
Alain Nussbaumer
6a84498645 [web] Fix French translation mistake 2023-12-08 03:19:28 +01:00
Alain Nussbaumer
418856bff1 [web] Bump versions of libraries 2023-12-07 22:17:41 +01:00
Alain Nussbaumer
c9971ad760 [web] Streamline imports 2023-12-07 22:14:23 +01:00
github-actions[bot]
4f73b3ecad [web] Rebuild web interface 2023-12-07 20:40:39 +00:00
Alain Nussbaumer
545f6c36c9 [web] Remove unused variables 2023-12-07 21:34:07 +01:00
Alain Nussbaumer
d0cd0c4bc7 [web]S Streamline code of pages 2023-12-07 21:31:10 +01:00
github-actions[bot]
0940950083 [web] Rebuild web interface 2023-12-07 16:38:48 +00:00
Alain Nussbaumer
e8363781af [web] Remove leading zero in time representations 2023-12-07 17:38:13 +01:00
github-actions[bot]
49c4984a77 [web] Rebuild web interface 2023-12-07 15:44:26 +00:00
Alain Nussbaumer
7456d958ba [web] Use CSS Bluma variables whenever possible 2023-12-07 16:43:47 +01:00
github-actions[bot]
e96a5702fd [web] Rebuild web interface 2023-12-07 14:58:07 +00:00
Alain Nussbaumer
a335989155 [web] Fix notification block for long messages
When long messages have to be displayed, their text no longer goes beyond the frame.
2023-12-07 15:57:16 +01:00
github-actions[bot]
0bd2cb4de3 [web] Rebuild web interface 2023-12-06 19:33:19 +00:00
Alain Nussbaumer
3fc349370f [web] Bump package version for security reasons 2023-12-06 20:32:48 +01:00
github-actions[bot]
99e49cdf6a [web] Rebuild web interface 2023-12-06 19:25:25 +00:00
Alain Nussbaumer
39847bab35 [web] Fix volume for stream not working in Safari 2023-12-06 20:24:55 +01:00
ejurgensen
85e9b06bca [xcode] Produce correctly sized ALAC frames
With ffmpeg < 6, using av_buffersink_set_frame_size() with the codecs frame
size means we no longer the frame size of 352 that Airplay requires.
2023-12-04 17:26:03 +01:00
ejurgensen
3ee9204ff8 [xcode/daap/rsp] Default transcode to 320 kbps mp3 instead of wav
- Calculate size for both formats (+ move the return to transcode_encode_query)
- Let transcode_needed() decide what format to output
- Determine content-type from transcoding type
- Add transcode-dependent ability to override file metadata in rsp/daap
- Send file size matching format
2023-12-04 17:26:03 +01:00
github-actions[bot]
9394d45de1 [web] Rebuild web interface 2023-11-29 10:49:55 +00:00
Alain Nussbaumer
a39229d7be [web] Fix to center content in the playback page 2023-11-29 11:49:24 +01:00
github-actions[bot]
ee94161eb4 [web] Rebuild web interface 2023-11-29 10:39:12 +00:00
Alain Nussbaumer
4332a43fb9 [web] Fix for full height not taking into account both navigation bars 2023-11-29 11:38:39 +01:00
github-actions[bot]
9f9420f713 [web] Rebuild web interface 2023-11-29 10:20:41 +00:00
Alain Nussbaumer
84d538e37f [web] Remove style class having no effect in this context 2023-11-29 11:18:43 +01:00
Alain Nussbaumer
9c1639d7d7 [web] Simplify CSS styling 2023-11-29 01:27:48 +01:00
github-actions[bot]
72206de234 [web] Rebuild web interface 2023-11-28 20:13:18 +00:00
Alain Nussbaumer
58b06ee5ff [web] Replace hard-coded values with Bulma variables 2023-11-28 21:12:42 +01:00
Alain Nussbaumer
bd70054ce9 [web] Remove useless line in the menu 2023-11-28 20:39:30 +01:00
Alain Nussbaumer
08b4dc3a52 [web] Fix web linting issue 2023-11-28 18:16:54 +01:00
github-actions[bot]
c5b88e1eaf [web] Rebuild web interface 2023-11-28 17:12:34 +00:00
Alain Nussbaumer
b783c39164 [web] Fix Vite warning 2023-11-28 18:11:57 +01:00
Alain Nussbaumer
a0c02ce022 [web] Bump to newer versions of libraries 2023-11-28 17:58:45 +01:00
Alain Nussbaumer
d627033141 [web] Improve the search of verse in the lyrics 2023-11-28 17:55:16 +01:00
github-actions[bot]
3577d0e2a8 [web] Rebuild web interface 2023-11-28 14:30:53 +00:00
Alain Nussbaumer
b55121bb4d
Merge pull request #1690 from X-Ryl669/variousCSSFix
[web] Various CSS fixes
2023-11-28 15:30:27 +01:00
X-Ryl669
47ad03b775 [web] Various CSS fixes 2023-11-28 14:23:25 +01:00
github-actions[bot]
bb6799eeb2 [web] Rebuild web interface 2023-11-27 18:16:55 +00:00
Alain Nussbaumer
ed40b5361e [web] Fix the cover art being distorted #1689 2023-11-27 19:16:21 +01:00
github-actions[bot]
4b5ea11ca0 [web] Rebuild web interface 2023-11-26 21:03:42 +00:00
Alain Nussbaumer
31406da911 [web] Merge previous commits 2023-11-26 22:01:30 +01:00
Alain Nussbaumer
a828356e0e [web] Fix height of cover art, preventing seeking the audio 2023-11-26 21:58:15 +01:00
Alain Nussbaumer
26089a05e0 [web] Fix linting warning 2023-11-26 21:55:43 +01:00
Alain Nussbaumer
b7ad3c8d45 [web] Make the lyrics pane code more readable 2023-11-26 21:39:56 +01:00
Alain Nussbaumer
553d35ef82 [web] Make code more readable 2023-11-26 16:49:37 +01:00
Alain Nussbaumer
66b1e444d1 [web] Format source code 2023-11-26 15:59:14 +01:00
Alain Nussbaumer
a9092e54c0 [web] Move parsing of lyrics to the lyrics pane 2023-11-26 15:38:45 +01:00
ejurgensen
f0df3f276f [library] Remove false log warning 2023-11-25 22:07:50 +01:00
github-actions[bot]
35a730793f [web] Rebuild web interface 2023-11-25 15:48:37 +00:00
Alain Nussbaumer
026e80ed64 [web] Remove console.log() output 2023-11-25 16:48:11 +01:00
github-actions[bot]
f0cc3ded00 [web] Rebuild web interface 2023-11-25 15:45:45 +00:00
Alain Nussbaumer
00e57a7473 [web] Fix linting error 2023-11-25 16:45:15 +01:00
github-actions[bot]
2e69720bc1 [web] Rebuild web interface 2023-11-25 15:39:25 +00:00
Alain Nussbaumer
f419869dfc [web] Fix a the lyrics pane overlapping the slider
When the track played has a composer or a long title, the slider is not covered by the lyrics pane anymore.
2023-11-25 16:38:50 +01:00
Alain Nussbaumer
d146a9e940 [web] Simplify lyrics button 2023-11-24 16:28:35 +01:00
Alain Nussbaumer
b39eb0b51d [web] Lint source code 2023-11-24 15:56:04 +01:00
Alain Nussbaumer
095d60af00 [web] Lint source code 2023-11-24 15:48:29 +01:00
Alain Nussbaumer
5c7ec031b5 [web] Format comments 2023-11-24 15:32:05 +01:00
github-actions[bot]
c84695a06e [web] Rebuild web interface 2023-11-24 12:58:59 +00:00
Alain Nussbaumer
91c1e5b174 [web] Fix for unexpected calls to the API #1688
Lyrics feature doesn't mess anymore when the user is playing tracks from Spotify
2023-11-24 13:58:30 +01:00
Alain Nussbaumer
f19e9fb48b [web] Remove unused code 2023-11-24 13:36:48 +01:00
github-actions[bot]
4c04bf6e15 [web] Rebuild web interface 2023-11-23 20:27:39 +00:00
Alain Nussbaumer
dae6d1eebc [web] Fix temporarily the bug #1688 2023-11-23 21:27:05 +01:00
github-actions[bot]
7d9b5dd948 [web] Rebuild web interface 2023-11-23 19:47:29 +00:00
Alain Nussbaumer
8e80503e81 [web] Make cancel button color match other dialog windows. 2023-11-23 20:45:24 +01:00
Alain Nussbaumer
cd42688357 [web] Fix text not being displayed when loading RSS feed 2023-11-23 20:43:31 +01:00
Alain Nussbaumer
f9d6056844 [Web] Correct French translation 2023-11-23 20:40:55 +01:00
Alain Nussbaumer
6741fcbc49 [web] Make title match action 2023-11-23 20:39:19 +01:00
Alain Nussbaumer
7142e87cf2 [web] Enforce 4-digit pins 2023-11-23 20:38:16 +01:00
Alain Nussbaumer
11616f5d32 [web] Reinforce check of URL entry 2023-11-23 20:36:55 +01:00
Alain Nussbaumer
406c87f765 [web] Change to template literals 2023-11-23 20:23:40 +01:00
github-actions[bot]
5cd63294a2 [web] Rebuild web interface 2023-11-21 16:09:36 +00:00
Alain Nussbaumer
f035a0ed3f [web] Format source code 2023-11-21 17:09:06 +01:00
github-actions[bot]
63fe5688ea [web] Rebuild web interface 2023-11-21 16:05:30 +00:00
Alain Nussbaumer
2f0e19cf41
Merge pull request #1687 from X-Ryl669/FixCenterLyricPos
[web] Ensure current lyric is centered
2023-11-21 17:05:01 +01:00
X-Ryl669
3fc149339b [web] Ensure current lyric is centered 2023-11-21 16:48:24 +01:00
github-actions[bot]
18e6afc4e0 [web] Rebuild web interface 2023-11-21 15:21:08 +00:00
Alain Nussbaumer
d2c4131d01
Merge pull request #1686 from X-Ryl669/FixFirstLyricBug
[web] Add spacing before first and after last lyric.
2023-11-21 16:20:41 +01:00
X-Ryl669
8d56f91117 [web] Add spacing before first and after last lyric. Fix a bug for the first lyric animation being triggerd before it'd have started 2023-11-21 15:48:24 +01:00
github-actions[bot]
59d7994de7 [web] Rebuild web interface 2023-11-21 14:45:38 +00:00
Alain Nussbaumer
28585bc217 [web] Fix "History" tab not being highlighted 2023-11-21 15:45:00 +01:00
github-actions[bot]
981f2dec36 [web] Rebuild web interface 2023-11-21 14:31:01 +00:00
Alain Nussbaumer
ef378088dd [web] Fix name of pages 2023-11-21 15:27:25 +01:00
Alain Nussbaumer
ed893d8774 [web] Update libraries to their latest versions 2023-11-21 15:26:34 +01:00
Alain Nussbaumer
d524a3e757 [web] Format source code 2023-11-21 15:26:13 +01:00
Alain Nussbaumer
356fcf8aa7 [web] Rename "Browse" tabs in the Music page to "History" #1685
At the same time, makes the naming of the underlying pages more coherent.
2023-11-21 15:25:27 +01:00
github-actions[bot]
d68ec8c075 [web] Rebuild web interface 2023-11-21 10:08:34 +00:00
Alain Nussbaumer
9cb961ef7f [web] Upgrade to newer version of libraries 2023-11-21 11:06:27 +01:00
github-actions[bot]
1cf7f81b5a [web] Rebuild web interface 2023-11-17 20:06:15 +00:00
Alain Nussbaumer
e01764d849
Merge pull request #1683 from X-Ryl669/LyricsCosmetic
[web] Fix pause blinking bug on lyrics pane
2023-11-17 21:05:50 +01:00
X-Ryl669
4574400ce2 [web] Fix pause blinking bug on lyrics pane 2023-11-17 20:48:22 +01:00
github-actions[bot]
d4cdf70d62 [web] Rebuild web interface 2023-11-17 19:15:24 +00:00
Alain Nussbaumer
47b0eef3bb
Merge pull request #1679 from X-Ryl669/LyricsWeb
Add lyrics player to the web interface.
2023-11-17 20:14:58 +01:00
X-Ryl669
ee1bd1ebda [web] Cosmetic changes for the lyrics pane. Also changed the current lyric playing model against the text wrap bug on smaller screen. 2023-11-17 20:07:07 +01:00
ejurgensen
812c46c572
Merge pull request #1682 from owntone/CDATA1
[misc] Add misc_xml.c to wrap mxml, fixes XML reading of CDATA (et al)
2023-11-13 16:53:03 +01:00
X-Ryl669
5e370e479a [web] Add lyrics player to the webinterface
Update icons.js
Add icons in alphabetical order.
Change comment to remove reference to external website
Remove extra line feeds

Co-Authored-by: Alain Nussbaumer <alain.nussbaumer@alleluia.ch>
2023-11-13 16:37:02 +01:00
ejurgensen
65c72c484b [misc] Add misc_xml.c to wrap mxml, fixes XML reading of CDATA (et al)
The change removes all direct calls to mxml from the modules that need an XML
parser (lastfm.c, pipe.c, rssscanner.c and httpd_rsp.c).

Even with the help of mxml, reading XML is hard, so a layer is added which
helps deal with stuff like whitespace and CDATA. This should make OwnTone more
resilient to any XML variations it might receive.

The changes fixes issue #1677.
2023-11-12 23:43:38 +01:00
ejurgensen
83ac327d7f [spotify] Don't log error if a playlist is empty
Fixes #1676
2023-10-28 17:38:37 +02:00
ejurgensen
369771bda5 [scan] Handle playlists with Unicode BOM
Closes #1674
2023-10-26 23:03:54 +02:00
ejurgensen
253a699001 [scan] Fix reading of FLAC tags
Regression from PR #1655. Closes #1673.
2023-10-26 22:40:23 +02:00
ejurgensen
9e9dc27a59
Merge pull request #1670 from owntone/gh_build_webui
Let Github actions rebuild web UI on push to web-src
2023-10-22 20:19:06 +02:00
ejurgensen
912e00d48d
Merge pull request #1671 from owntone/dependabot/npm_and_yarn/web-src/postcss-8.4.31
Bump postcss from 8.4.27 to 8.4.31 in /web-src
2023-10-22 00:04:45 +02:00
dependabot[bot]
8049760703
Bump postcss from 8.4.27 to 8.4.31 in /web-src
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-21 22:02:32 +00:00
ejurgensen
84042a4514 [gh-actions] No compilation workflows if just docs/htdocs/web-src changed 2023-10-21 23:27:47 +02:00
ejurgensen
986fc55dbd [gh-actions] Add rebuild of web UI workflow on push to web-src 2023-10-21 23:26:47 +02:00
ejurgensen
a9e20abf06 Revert "[web] Add lyrics player to the webinterface"
This reverts commit 98a844b4090b20fbb194ddf7d9d7a48f2f7a201c.
2023-10-21 22:41:45 +02:00
ejurgensen
e4c47c22b3 [scan] Fix issue where m3u genre doesn't override despite m3u_override = true
Closes #1668
2023-10-21 22:37:34 +02:00
ejurgensen
5fb41171d6 [gh-actions] Add web UI lint as a PR and push check 2023-10-21 22:33:54 +02:00
ejurgensen
c7f71b478f [gh-actions] Update checkout version to latest (v4) 2023-10-21 22:32:35 +02:00
ejurgensen
43e95e8ba7 [gh-actions] Also update version of codeql/analyze to v2 2023-10-21 14:47:49 +02:00
ejurgensen
429178e518 [gh-actions] Update checkout and codeql versions 2023-10-21 14:41:12 +02:00
ejurgensen
4365869fb1
Merge pull request #1655 from X-Ryl669/lyrics
Add support for Lyrics
2023-10-20 16:20:30 +02:00
ejurgensen
8796368b01 [scan] Fixup lyrics changes 2023-10-19 23:49:53 +02:00
X-Ryl669
98a844b409 [web] Add lyrics player to the webinterface
Update icons.js
Add icons in alphabetical order.
Change comment to remove reference to external website
Remove extra line feeds

Co-Authored-by: Alain Nussbaumer <alain.nussbaumer@alleluia.ch>
2023-10-19 23:49:53 +02:00
X-Ryl669
9670f6b079 [httpd] Add fetching lyrics in the JSON api 2023-10-19 23:49:53 +02:00
X-Ryl669
cf8b3ecd3a [db,library] Add support for parsing lyrics and storing them in DB 2023-10-19 23:49:53 +02:00
Alain Nussbaumer
d7d3a0767d [web] Correct the use of Bulma variables in the CSS
The variables for colors are now correctly referenced in the CSS.
2023-10-08 10:47:26 +02:00
ejurgensen
67de2303f9
Merge pull request #1663 from owntone/esp32
[raop] Make compressed ALAC default, but with a config option
2023-10-07 09:32:40 +02:00
ejurgensen
d266c8a56f [raop] Make compressed ALAC default, but with a config option
Closes #1656
2023-10-06 21:39:26 +02:00
ejurgensen
c34acb16c2
Merge pull request #1660 from X-Ryl669/fixSelect
Fix select issue on FreeBSD.
2023-10-02 11:15:39 +02:00
X-Ryl669
ab790c2880 [mdns] Fix select issue on FreeBSD.
As reported in issue #1654, using select to test for a (non-blocking) connection success crashes on FreeBSD when the number of opened file descriptor is higher than FDSET_SIZE.
Instead of returning with an error in that case, this commit uses poll instead that's not limited to the number of opened file descriptors, preventing an out-of-bound write.
2023-10-02 09:53:06 +02:00
ejurgensen
8528073003 [daap] Fix for use-after-free
Present in 28.7 and 28.8. Closes #1659.

Credit X-Ryl669.
2023-09-30 23:06:09 +02:00
ejurgensen
4662cd4cce [scan] Set consistent return type in check_path_in_directories() 2023-09-27 22:46:13 +02:00
ejurgensen
85929dcaa8 Bump version to 28.8 2023-09-05 22:55:47 +02:00
ejurgensen
1aec50bcfd ChangeLog for OwnTone 28.8 2023-09-05 22:54:45 +02:00
ejurgensen
89c148411e [gh-actions] Add MacOS run test 2023-09-03 21:16:44 +02:00
ejurgensen
e850549aa1 [gh-actions] Run test with standard install method 2023-09-03 21:01:11 +02:00
ejurgensen
d5335317a6 [gh-actions] Prepare workflow with runtest 2023-09-03 21:01:11 +02:00
ejurgensen
a9e21dcbfd [main] Add a 'testrun' command line option for Github actions 2023-09-03 21:01:11 +02:00
ejurgensen
3f6c7405ed [spotify] Don't zero pthread lock when resetting credentials 2023-09-03 20:59:45 +02:00
ejurgensen
6742272221 [gh-actions] Add workflow-dispatch to MacOS workflow 2023-09-02 23:30:42 +02:00
ejurgensen
8b64bb4cd8 [docs] Add link to blog about authentication with Authelia for remote access
Credit @X-Ryl669

Closes #1647
2023-09-02 23:07:05 +02:00
ejurgensen
54c2667aea [misc] Use fcntl+O_NONBLOCK when binding instead of socket+SOCK_NONBLOCK
socket() with SOCK_NONBLOCK (O_NONBLOCK) seems not to be possible on MacOS, it
yields 'Protocol wrong type for socket'. Switch to using fcntl() and O_NONBLOCK
instead, hopefully works better cross-platform.

Closes #1644
2023-09-01 17:13:33 +02:00
ejurgensen
9d092c983b
Merge pull request #1648 from whatdoineed2do/library-overwrite-db-entry-fix 2023-09-01 17:06:44 +02:00
whatdoineed2do/Ray
b9b8ced689 [scan] Handles case for file overwrite/move within library that previously original db entry for overwritten file
Fixes #1645
2023-09-01 09:17:54 +01:00
ejurgensen
0d94681f16 [gh-actions] Add workflow-dispatch to Ubuntu workflow 2023-08-31 22:59:26 +02:00
ejurgensen
174aa86033
Merge pull request #1646 from owntone/raop_alac_endtag1
[raop] Add ALAC end tag to each frame
2023-08-31 17:10:06 +02:00
ejurgensen
b9da6bc80d Revert "[gh-actions] Update macos.yml, add brew update step"
This reverts commit 0f33a896deb14a1cd0380b35d9d663b84b79348b.

Reason is that using newest homebrew seems to make the workflow very unstable.
2023-08-30 21:16:18 +02:00
ejurgensen
447e042953 [raop] Add ALAC end tag to each frame
The missing end tag makes the ffmpeg alac decoder upset and we can't have that

Credit Mike Brady
2023-08-30 21:00:33 +02:00
ejurgensen
4315c73775 [httpd] Fix building on FreeBSD which doesn't have SYS_gettid 2023-08-29 23:50:19 +02:00
ejurgensen
214ef12cb5 [spotify] Fix free of invalid pointer on http request error
Fixes #1643
2023-08-29 20:14:19 +02:00
438 changed files with 39918 additions and 24332 deletions

245
.clang-format Normal file
View File

@ -0,0 +1,245 @@
---
Language: Cpp
AccessModifierOffset: -2
AlignAfterOpenBracket: DontAlign
AlignArrayOfStructures: Left
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: All
AlwaysBreakAfterReturnType: All
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: false
AfterExternBlock: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: false
AfterUnion: true
BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: false
BeforeWhile: true
IndentBraces: true
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Custom
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: false
AfterForeachMacros: false
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: false
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: c++03
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: ForIndentation
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...

7
.dev/Makefile Normal file
View File

@ -0,0 +1,7 @@
.PHONY: vscode
vscode:
mkdir -p ../.vscode
cp -rT ./vscode ../.vscode
mkdir -p ../.devcontainer
cp -rT ./devcontainer ../.devcontainer

View File

@ -0,0 +1,20 @@
#!/usr/bin/env sh
# cd aliases
alias ..='cd ..'
alias ...='cd ../..'
alias -- -='cd -'
# bat aliases
alias bat='batcat'
if [ "$ENABLE_ESA" = "1" ]; then
if [ "$(command -v eza)" ]; then
alias l='eza -la --icons=auto --group-directories-first'
alias la='eza -la --icons=auto --group-directories-first'
alias ll='eza -l --icons=auto --group-directories-first'
alias l.='eza -d .*'
alias ls='eza'
alias l1='eza -1'
fi
fi

View File

@ -0,0 +1,30 @@
#!/bin/sh
# Install mkdocs with mkdocs-material theme
pipx install --include-deps mkdocs-material
pipx inject mkdocs-material mkdocs-minify-plugin
# Starfish (https://starship.rs/) - shell prompt
if [ "$ENABLE_STARSHIP" = "1" ]
then
curl -sS https://starship.rs/install.sh | sh -s -- -y
echo 'eval "$(starship init bash)"' >> ~/.bashrc
fi
# Atuin (https://atuin.sh/) - shell history
if [ "$ENABLE_ATUIN" = "1" ]
then
curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh
curl https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh -o ~/.bash-preexec.sh
fi
# zoxide (https://github.com/ajeetdsouza/zoxide) - replacement for cd
if [ "$ENABLE_ZOXIDE" = "1" ]
then
curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
echo 'eval "$(zoxide init bash)"' >> ~/.bashrc
fi
pipx install harlequin
pipx install toolong
pipx install posting

View File

@ -0,0 +1,479 @@
# A quick guide to configuring OwnTone:
#
# For regular use, the most important setting to configure is "directories",
# which should be the location of your media. Whatever user you have set as
# "uid" must have read access to this location. If the location is a network
# mount, please see the README.
#
# In all likelihood, that's all you need to do!
general {
# Username
# Make sure the user has read access to the library directories you set
# below, and full access to the databases, log and local audio
uid = "vscode"
# Database location
db_path = "/data/cache/songs3.db"
# Database backup location
# Uncomment and specify a full path to enable abilty to use REST endpoint
# to initiate backup of songs3.db
# db_backup_path = "/usr/local/var/cache/owntone/songs3.bak"
# Log file and level
# Available levels: fatal, log, warning, info, debug, spam
logfile = "/data/logs/owntone.log"
loglevel = debug
# Admin password for the web interface
# Note that access to the web interface from computers in
# "trusted_network" (see below) does not require password
# admin_password = ""
# Websocket port for the web interface.
# websocket_port = 0
# Websocket interface to bind listener to (e.g. "eth0"). Default is
# disabled, which means listen on all interfaces.
# websocket_interface = ""
# Sets who is allowed to connect without authorisation. This applies to
# client types like Remotes, DAAP clients (iTunes) and to the web
# interface. Options are "any", "localhost" or the prefix to one or
# more ipv4/6 networks. The default is { "localhost", "192.168", "fd" }
# trusted_networks = { "localhost", "192.168", "fd" }
# Enable/disable IPv6
# ipv6 = no
# Set this if you want the server to bind to a specific IP address. Can
# be ipv6 or ipv4. Default (commented out or "::") is to listen on all
# IP addresses.
# bind_address = "::"
# Location of cache database
cache_path = "/data/cache/cache.db"
# DAAP requests that take longer than this threshold (in msec) get their
# replies cached for next time. Set to 0 to disable caching.
# cache_daap_threshold = 1000
# When starting playback, autoselect speaker (if none of the previously
# selected speakers/outputs are available)
# speaker_autoselect = no
# Most modern systems have a high-resolution clock, but if you are on an
# unusual platform and experience audio drop-outs, you can try changing
# this option
# high_resolution_clock = yes
}
# Library configuration
library {
# Name of the library as displayed by the clients (%h: hostname). If you
# change the name after pairing with Remote you may have to re-pair.
name = "My Music on %h"
# TCP port to listen on. Default port is 3689 (daap)
port = 3689
# Password for the library. Optional.
# password = ""
# Directories to index
directories = { "/data/music" }
# Follow symlinks. Default: true.
# follow_symlinks = true
# Directories containing podcasts
# For each directory that is indexed the path is matched against these
# names. If there is a match all items in the directory are marked as
# podcasts. Eg. if you index /srv/music, and your podcasts are in
# /srv/music/Podcasts, you can set this to "/Podcasts".
# (changing this setting only takes effect after rescan, see the README)
podcasts = { "/Podcasts" }
# Directories containing audiobooks
# For each directory that is indexed the path is matched against these
# names. If there is a match all items in the directory are marked as
# audiobooks.
# (changing this setting only takes effect after rescan, see the README)
audiobooks = { "/Audiobooks" }
# Directories containing compilations (eg soundtracks)
# For each directory that is indexed the path is matched against these
# names. If there is a match all items in the directory are marked as
# compilations.
# (changing this setting only takes effect after rescan, see the README)
compilations = { "/Compilations" }
# Compilations usually have many artists, and sometimes no album artist.
# If you don't want every artist to be listed in artist views, you can
# set a single name which will be used for all compilation tracks
# without an album artist, and for all tracks in the compilation
# directories.
# (changing this setting only takes effect after rescan, see the README)
compilation_artist = "Various Artists"
# If your album and artist lists are cluttered, you can choose to hide
# albums and artists with only one track. The tracks will still be
# visible in other lists, e.g. songs and playlists. This setting
# currently only works in some remotes.
# hide_singles = false
# Internet streams in your playlists will by default be shown in the
# "Radio" library, like iTunes does. However, some clients (like
# TunesRemote+) won't show the "Radio" library. If you would also like
# to have them shown like normal playlists, you can enable this option.
# radio_playlists = false
# These are the default playlists. If you want them to have other names,
# you can set it here.
# name_library = "Library"
# name_music = "Music"
# name_movies = "Movies"
# name_tvshows = "TV Shows"
# name_podcasts = "Podcasts"
# name_audiobooks = "Audiobooks"
# name_radio = "Radio"
# Artwork file names (without file type extension)
# OwnTone will look for jpg and png files with these base names
# artwork_basenames = { "artwork", "cover", "Folder" }
# Enable searching for artwork corresponding to each individual media
# file instead of only looking for album artwork. This is disabled by
# default to reduce cache size.
# artwork_individual = false
# File types the scanner should ignore
# Non-audio files will never be added to the database, but here you
# can prevent the scanner from even probing them. This might improve
# scan time. By default .db, .ini, .db-journal, .pdf and .metadata are
# ignored.
# filetypes_ignore = { ".db", ".ini", ".db-journal", ".pdf", ".metadata" }
# File paths the scanner should ignore
# If you want to exclude files on a more advanced basis you can enter
# one or more POSIX regular expressions, and any file with a matching
# path will be ignored.
# filepath_ignore = { "myregex" }
# Disable startup file scanning
# When OwnTone starts it will do an initial file scan of your
# library (and then watch it for changes). If you are sure your library
# never changes while OwnTone is not running, you can disable the
# initial file scan and save some system ressources. Disabling this scan
# may lead to OwnTone's database coming out of sync with the
# library. If that happens read the instructions in the README on how
# to trigger a rescan.
# filescan_disable = false
# Only use the first genre found in metadata
# Some tracks have multiple genres semicolon-separated in the same tag,
# e.g. 'Pop;Rock'. If you don't want them listed like this, you can
# enable this option and only the first genre will be used (i.e. 'Pop').
# only_first_genre = false
# Should metadata from m3u playlists, e.g. artist and title in EXTINF,
# override the metadata we get from radio streams?
# m3u_overrides = false
# Should iTunes metadata override ours?
# itunes_overrides = false
# Should we import the content of iTunes smart playlists?
# itunes_smartpl = false
# Decoding options for DAAP and RSP clients
# Since iTunes has native support for mpeg, mp4a, mp4v, alac and wav,
# such files will be sent as they are. Any other formats will be decoded
# to raw wav. If OwnTone detects a non-iTunes DAAP client, it is
# assumed to only support mpeg and wav, other formats will be decoded.
# Here you can change when to decode. Note that these settings only
# affect serving media to DAAP and RSP clients, they have no effect on
# direct AirPlay, Chromecast and local audio playback.
# Formats: mp4a, mp4v, mpeg, alac, flac, mpc, ogg, wma, wmal, wmav, aif, wav
# Formats that should never be decoded
# no_decode = { "format", "format" }
# Formats that should always be decoded
# force_decode = { "format", "format" }
# Set ffmpeg filters (similar to 'ffmpeg -af xxx') that you want the
# server to use when decoding files from your library. Examples:
# { 'volume=replaygain=track' } -> use REPLAYGAIN_TRACK_GAIN metadata
# { 'loudnorm=I=-16:LRA=11:TP=-1.5' } -> normalize volume
# decode_audio_filters = { }
# Watch named pipes in the library for data and autostart playback when
# there is data to be read. To exclude specific pipes from watching,
# consider using the above _ignore options.
# pipe_autostart = true
# Enable automatic rating updates
# If enabled, rating is automatically updated after a song has either been
# played or skipped (only skipping to the next song is taken into account).
# The calculation is taken from the beets plugin "mpdstats" (see
# https://beets.readthedocs.io/en/latest/plugins/mpdstats.html).
# It consist of calculating a stable rating based only on the play- and
# skipcount and a rolling rating based on the current rating and the action
# (played or skipped). Both results are combined with a mix-factor of 0.75:
# new rating = 0.75 * stable rating + 0.25 * rolling rating)
# rating_updates = false
# By default, ratings are only saved in the server's database. Enable
# the below to make the server also read ratings from file metadata and
# write on update (requires write access). To avoid excessive writing to
# the library, automatic rating updates are not written, even with the
# write_rating option enabled.
# read_rating = false
# write_rating = false
# The scale used when reading/writing ratings to files
# max_rating = 100
# Allows creating, deleting and modifying m3u playlists in the library directories.
# Only supported by the player web interface and some mpd clients
# Defaults to being disabled.
# allow_modifying_stored_playlists = false
# A directory in one of the library directories that will be used as the default
# playlist directory. OwnTone creates new playlists in this directory if only
# a playlist name is provided (requires "allow_modify_stored_playlists" set to true).
# default_playlist_directory = ""
# By default OwnTone will - like iTunes - clear the playqueue if
# playback stops. Setting clear_queue_on_stop_disable to true will keep
# the playlist like MPD does. Note that some dacp clients do not show
# the playqueue if playback is stopped.
# clear_queue_on_stop_disable = false
}
# Local audio output
audio {
# Name - used in the speaker list in Remote
nickname = "Computer"
# Type of the output (alsa, pulseaudio, dummy or disabled)
# type = "alsa"
# For pulseaudio output, an optional server hostname or IP can be
# specified (e.g. "localhost"). If not set, connection is made via local
# socket.
# server = ""
# Audio PCM device name for local audio output - ALSA only
# card = "default"
# Mixer channel to use for volume control - ALSA only
# If not set, PCM will be used if available, otherwise Master.
# mixer = ""
# Mixer device to use for volume control - ALSA only
# If not set, the value for "card" will be used.
# mixer_device = ""
# Enable or disable audio resampling to keep local audio in sync with
# e.g. Airplay. This feature relies on accurate ALSA measurements of
# delay, and some devices don't provide that. If that is the case you
# are better off disabling the feature.
# sync_disable = false
# Here you can adjust when local audio is started relative to other
# speakers, e.g. Airplay. Negative values correspond to moving local
# audio ahead, positive correspond to delaying it. The unit is
# milliseconds. The offset must be between -1000 and 1000 (+/- 1 sec).
# offset_ms = 0
# To calculate what and if resampling is required, local audio delay is
# measured each second. After a period the collected measurements are
# used to estimate drift and latency, which determines if corrections
# are required. This setting sets the length of that period in seconds.
# adjust_period_seconds = 100
}
# ALSA device settings
# If you have multiple ALSA devices you can configure them individually via
# sections like the below. Make sure to set the "card name" correctly. See the
# README about ALSA for details. Note that these settings will override the ALSA
# settings in the "audio" section above.
#alsa "card name" {
# Name used in the speaker list. If not set, the card name will be used.
# nickname = "Computer"
# Mixer channel to use for volume control
# If not set, PCM will be used if available, otherwise Master
# mixer = ""
# Mixer device to use for volume control
# If not set, the card name will be used
# mixer_device = ""
#}
# Pipe output
# Allows OwnTone to output audio data to a named pipe
#fifo {
# nickname = "fifo"
# path = "/path/to/fifo"
#}
# AirPlay settings common to all devices
#airplay_shared {
# UDP ports used when airplay devices make connections back to
# OwnTone (choosing specific ports may be helpful when running
# OwnTone behind a firewall)
# control_port = 0
# timing_port = 0
# Switch Airplay 1 streams to uncompressed ALAC (as opposed to regular,
# compressed ALAC). Reduces CPU use at the cost of network bandwidth.
# uncompressed_alac = false
#}
# AirPlay per device settings
# (make sure you get the capitalization of the device name right)
#airplay "My AirPlay device" {
# OwnTone's volume goes to 11! If that's more than you can handle
# you can set a lower value here
# max_volume = 11
# Enable this option to exclude a particular AirPlay device from the
# speaker list
# exclude = false
# Enable this option to keep a particular AirPlay device in the speaker
# list and thus ignore mdns notifications about it no longer being
# present. The speaker will remain until restart of OwnTone.
# permanent = false
# Some devices spuriously disconnect during playback, and based on the
# device type OwnTone may attempt to reconnect. Setting this option
# overrides this so reconnecting is either always enabled or disabled.
# reconnect = false
# AirPlay password
# password = "s1kr3t"
# Disable AirPlay 1 (RAOP)
# raop_disable = false
# Name used in the speaker list, overrides name from the device
# nickname = "My speaker name"
#}
# Chromecast settings
# (make sure you get the capitalization of the device name right)
#chromecast "My Chromecast device" {
# OwnTone's volume goes to 11! If that's more than you can handle
# you can set a lower value here
# max_volume = 11
# Enable this option to exclude a particular device from the speaker
# list
# exclude = false
# Name used in the speaker list, overrides name from the device
# nickname = "My speaker name"
#}
# Spotify settings (only have effect if Spotify enabled - see README/INSTALL)
spotify {
# Set preferred bitrate for music streaming
# 0: No preference (default), 1: 96kbps, 2: 160kbps, 3: 320kbps
# bitrate = 0
# Your Spotify playlists will by default be put in a "Spotify" playlist
# folder. If you would rather have them together with your other
# playlists you can set this option to true.
# base_playlist_disable = false
# Spotify playlists usually have many artist, and if you don't want
# every artist to be listed when artist browsing in Remote, you can set
# the artist_override flag to true. This will use the compilation_artist
# as album artist for Spotify items.
# artist_override = false
# Similar to the different artists in Spotify playlists, the playlist
# items belong to different albums, and if you do not want every album
# to be listed when browsing in Remote, you can set the album_override
# flag to true. This will use the playlist name as album name for
# Spotify items. Notice that if an item is in more than one playlist,
# it will only appear in one album when browsing (in which album is
# random).
# album_override = false
}
# RCP/Roku Soundbridge output settings
# (make sure you get the capitalization of the device name right)
#rcp "My SoundBridge device" {
# Enable this option to exclude a particular device from the speaker
# list
# exclude = false
# A Roku/SoundBridge can power up in 2 modes: (default) reconnect to the
# previously used library (ie OwnTone) or in a 'cleared library' mode.
# The Roku power up behaviour is affected by how OwnTone disconnects
# from the Roku device.
#
# Set to false to maintain default Roku power on behaviour
# clear_on_close = false
#}
# MPD configuration (only have effect if MPD enabled - see README/INSTALL)
mpd {
# TCP port to listen on for MPD client requests.
# Default port is 6600, set to 0 to disable MPD support.
# port = 6600
# HTTP port to listen for artwork requests (only supported by some MPD
# clients and will need additional configuration in the MPD client to
# work). Set to 0 to disable serving artwork over http.
# http_port = 0
}
# SQLite configuration (allows to modify the operation of the SQLite databases)
# Make sure to read the SQLite documentation for the corresponding PRAGMA
# statements as changing them from the defaults may increase the possibility of
# database corruptions! By default the SQLite default values are used.
sqlite {
# Cache size in number of db pages for the library database
# (SQLite default page size is 1024 bytes and cache size is 2000 pages)
# pragma_cache_size_library = 2000
# Cache size in number of db pages for the daap cache database
# (SQLite default page size is 1024 bytes and cache size is 2000 pages)
# pragma_cache_size_cache = 2000
# Sets the journal mode for the database
# DELETE (default), TRUNCATE, PERSIST, MEMORY, WAL, OFF
# pragma_journal_mode = DELETE
# Change the setting of the "synchronous" flag
# 0: OFF, 1: NORMAL, 2: FULL (default)
# pragma_synchronous = 2
# Number of bytes set aside for memory-mapped I/O for the library database
# (requires sqlite 3.7.17 or later)
# 0: disables mmap (default), any other value > 0: number of bytes for mmap
# pragma_mmap_size_library = 0
# Number of bytes set aside for memory-mapped I/O for the cache database
# (requires sqlite 3.7.17 or later)
# 0: disables mmap (default), any other value > 0: number of bytes for mmap
# pragma_mmap_size_cache = 0
# Should the database be vacuumed on startup? (increases startup time,
# but may reduce database size). Default is yes.
# vacuum = yes
}
# Streaming audio settings for remote connections (ie stream.mp3)
streaming {
# Sample rate, typically 44100 or 48000
# sample_rate = 44100
# Set the MP3 streaming bit rate (in kbps), valid options: 64 / 96 / 128 / 192 / 320
# bit_rate = 192
}

View File

@ -0,0 +1,11 @@
# Starfish (https://starship.rs/) - shell prompt
ENABLE_STARSHIP=1
# Atuin (https://atuin.sh/) - shell history
ENABLE_ATUIN=1
# zoxide (https://github.com/ajeetdsouza/zoxide) - replacement for cd
ENABLE_ZOXIDE=1
# eza (https://eza.rocks/) - replacement for ls
ENABLE_ESA=1

View File

@ -0,0 +1,73 @@
FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04
ARG USERNAME=vscode
# Workaround for bug: https://github.com/devcontainers/images/issues/1056
RUN userdel -r ubuntu; usermod -u 1000 $USERNAME; groupmod -g 1000 $USERNAME
RUN apt-get -y update \
&& apt-get install -y \
# Build tools and dependencies for OwnTone
autoconf \
automake \
autotools-dev \
bison \
build-essential \
flex \
gawk \
gettext \
git \
gperf \
libasound2-dev \
libavahi-client-dev \
libavcodec-dev \
libavfilter-dev \
libavformat-dev \
libavutil-dev \
libconfuse-dev \
libcurl4-openssl-dev \
libevent-dev \
libgcrypt20-dev \
libjson-c-dev \
libmxml-dev \
libplist-dev \
libprotobuf-c-dev \
libsodium-dev \
libsqlite3-dev \
libswscale-dev \
libtool \
libunistring-dev \
libwebsockets-dev \
libxml2-dev \
zlib1g-dev \
# Build tools for mmkdocs (OwnTone documentation)
python3-pip \
# Additional runtime dependencies for dev container
avahi-daemon \
# Additional debug and devtools for dev container
clang \
clang-format \
clang-tools \
gdb \
valgrind \
# Additional terminal utility applications
pipx \
# bat - replacement for cat
bat \
# eza (https://eza.rocks/) - replacement for ls
eza \
# fuzzy search
fzf \
# Create folders and set ownership for folders that might be mounted as volumes
&& mkdir -p /home/$USERNAME/.local/share \
&& chown -R $USERNAME /home/$USERNAME/.local \
&& mkdir /commandhistory \
&& touch /commandhistory/.bash_history \
&& chown -R $USERNAME /commandhistory \
&& echo "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" >> "/home/$USERNAME/.bashrc" \
&& echo '[[ -f /scripts/init-devcontainer-cli.sh ]] && source /scripts/init-devcontainer-cli.sh' >> "/home/$USERNAME/.bashrc" \
# Create folders for owntone-server data
&& mkdir -p /data/logs /data/music /data/cache /data/conf \
&& chown -R $USERNAME /data \
# Clean up
&& apt-get clean -y && rm -rf /var/lib/apt/lists/*

View File

@ -0,0 +1,55 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
{
"name": "Ubuntu",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
// "image": "mcr.microsoft.com/devcontainers/base:jammy"
"build": {
"dockerfile": "Dockerfile"
},
"runArgs": [
// Use host network to be able to connect to remote speakers
"--network=host",
"--env-file", ".devcontainer/devcontainer.env"
],
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"Vue.volar",
"ms-vscode.cpptools-extension-pack",
"ms-azuretools.vscode-docker",
"lokalise.i18n-ally",
"esbenp.prettier-vscode"
]
}
},
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/node:1": {}
},
// Mounts volumes to keep files / state between container rebuilds
"mounts": [
// Map script folder to install and init additional tools (see "postCreateCommand" and Dockerfile / .bashrc)
"source=${localWorkspaceFolder}/.devcontainer/.scripts,target=/scripts,type=bind,consistency=cached",
// Persist ~/.bash_history
"source=owntone-bashhistory,target=/commandhistory,type=volume",
// Persist ~/.local/share to persist state of additionally installed tools (e. g. atuin, zoxide)
"source=owntone-localshare,target=/home/vscode/.local/share,type=volume",
// Bind mounts for owntone config file and logs, cache, music directories
//"source=<path-to-local-logs-dir>,target=/data/logs,type=bind,consistency=cached",
//"source=<path-to-local-cache-dir>,target=/data/cache,type=bind,consistency=cached",
//"source=<path-to-local-music-dir>,target=/data/music,type=bind,consistency=cached",
"source=${localWorkspaceFolder}/.devcontainer/data/devcontainer-owntone.conf,target=/data/conf/owntone.conf,type=bind,consistency=cached"
],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "bash /scripts/install-devcontainer-tools.sh",
// Start dbus and avahi, required when running owntone-server
"postStartCommand": "sudo service dbus start ; sudo avahi-daemon -D"
}

View File

@ -0,0 +1,17 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/usr/include/json-c"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "linux-clang-x64"
}
],
"version": 4
}

28
.dev/vscode/launch.json Normal file
View File

@ -0,0 +1,28 @@
{
"configurations": [
{
"name": "OwnTone",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/src/owntone",
"args": ["-f", "-c", "/data/conf/owntone.conf", "-w", "${workspaceFolder}/htdocs", "-s", "${workspaceFolder}/sqlext/.libs/owntone-sqlext.so"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
],
"version": "2.0.0"
}

12
.dev/vscode/settings.json Normal file
View File

@ -0,0 +1,12 @@
{
"C_Cpp.default.forcedInclude": [
"${workspaceFolder}/config.h"
],
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[c]": {
"editor.detectIndentation": false,
"editor.tabSize": 8
}
}

106
.dev/vscode/tasks.json Normal file
View File

@ -0,0 +1,106 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "[server] autoreconf",
"type": "shell",
"command": "autoreconf -i",
"group": {
"kind": "build",
"isDefault": false
}
},
{
"label": "[server] configure",
"type": "shell",
"command": "./configure",
"group": {
"kind": "build",
"isDefault": false
}
},
{
"label": "[server] make",
"type": "shell",
"command": "make",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "[server] scan-build",
"type": "shell",
"command": "make clean && scan-build --status-bugs -disable-checker deadcode.DeadStores --exclude src/parsers make",
"group": {
"kind": "build",
"isDefault": false
}
},
{
"label": "[server] clean",
"type": "shell",
"command": "make clean",
"group": {
"kind": "build",
"isDefault": false
}
},
{
"label": "[web] install",
"type": "npm",
"script": "install",
"options": {
"cwd": "${workspaceFolder}/web-src"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "[web] build",
"type": "npm",
"script": "build",
"options": {
"cwd": "${workspaceFolder}/web-src"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "[web] serve",
"type": "npm",
"script": "serve",
"options": {
"cwd": "${workspaceFolder}/web-src"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "[docs] serve",
"type": "shell",
"command": "mkdocs serve",
"group": {
"kind": "build",
"isDefault": false
}
},
{
"label": "[docs] build",
"type": "shell",
"command": "mkdocs build",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -13,4 +13,4 @@ Please try to provide the following:
- version of owntone
- platform
Steps to reproduce will greatly improve the chance of getting it fixed. If it is not possible then set the log level to debug (in /etc/owntone.conf) and try to get some logging of when the error happens. Dont cut and paste lengthy log outtakes here on github. Instead, attach the log file.
Steps to reproduce will greatly improve the chance of getting it fixed. If it is not possible then set the log level to debug (in /etc/owntone.conf) and try to get some logging of when the error happens. Dont cut and paste lengthy log outtakes here on github. Instead, attach the log file. Remove parts of the log file that aren't relevant and also make sure to delete Spotify username/tokens.

39
.github/workflows/build_htdocs.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: Build htdocs
on:
push:
branches:
- master
paths:
- 'web-src/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
working-directory: web-src
run: npm install
# Build for production with minification (will update web interface
# in "../htdocs")
- name: Build htdocs
working-directory: web-src
run: npm run build
- name: Count changed files
id: count
run: |
git add htdocs/
git diff --numstat --staged > diffstat
test -s diffstat || { echo "Warning: Push to web-src did not change htdocs"; exit 1; }
# The GH action email is from https://github.com/orgs/community/discussions/26560
- name: Commit and push updated assets
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "[web] Rebuild web interface"
git push

View File

@ -2,10 +2,19 @@ name: "CodeQL"
on:
push:
branches: [master, ]
branches:
- master
paths-ignore:
- 'docs/**'
- 'htdocs/**'
- 'web-src/**'
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
branches:
- master
paths-ignore:
- 'docs/**'
- 'htdocs/**'
- 'web-src/**'
schedule:
- cron: '0 19 * * 6'
@ -16,11 +25,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
with:
config-file: ./.github/codeql/codeql-config.yml
# Override language selection by uncommenting this and choosing your languages
@ -30,7 +39,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
#- name: Autobuild
# uses: github/codeql-action/autobuild@v1
# uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -40,10 +49,10 @@ jobs:
# uses a compiled language
- run: |
sudo apt-get update
sudo apt-get install -yq build-essential clang clang-tools git autotools-dev autoconf libtool gettext gawk gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev libevent-dev libplist-dev libsodium-dev libcurl4-openssl-dev libjson-c-dev libprotobuf-c-dev libpulse-dev libwebsockets-dev libgnutls28-dev
sudo apt-get install -yq build-essential clang clang-tools git autotools-dev autoconf libtool gettext gawk gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev libasound2-dev libxml2-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev libevent-dev libplist-dev libsodium-dev libcurl4-openssl-dev libjson-c-dev libprotobuf-c-dev libpulse-dev libwebsockets-dev libgnutls28-dev
autoreconf -vi
./configure --enable-lastfm --enable-chromecast
scan-build --status-bugs -disable-checker deadcode.DeadStores --exclude src/parsers make
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v3

44
.github/workflows/freebsd.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: FreeBSD
on:
push:
branches:
- master
paths-ignore:
- 'docs/**'
- 'htdocs/**'
- 'web-src/**'
pull_request:
branches:
- master
paths-ignore:
- 'docs/**'
- 'htdocs/**'
- 'web-src/**'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
uses: vmactions/freebsd-vm@v1
with:
prepare: |
pkg install -y gmake autoconf automake libtool pkgconf gettext gperf glib ffmpeg libconfuse libevent libxml2 libgcrypt libunistring libiconv curl libplist libinotify avahi sqlite3 alsa-lib libsodium json-c libwebsockets protobuf-c bison flex
pw user add owntone -m -d /usr/local/var/cache/owntone
run: |
autoreconf -vi
export CFLAGS="${ARCH} -g -I/usr/local/include -I/usr/include"
export LDFLAGS="-L/usr/local/lib -L/usr/lib"
./configure --disable-install-system
gmake
gmake install
mkdir -p /srv/music
service dbus onestart
service avahi-daemon onestart
/usr/local/sbin/owntone -f -t

View File

@ -7,7 +7,7 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: actions/setup-python@v2
with:
python-version: 3.x

View File

@ -1,35 +1,42 @@
name: MacOS
name: macOS
on:
push:
branches: [ master ]
branches:
- master
paths-ignore:
- 'docs/**'
- 'htdocs/**'
- 'web-src/**'
pull_request:
branches: [ master ]
branches:
- master
paths-ignore:
- 'docs/**'
- 'htdocs/**'
- 'web-src/**'
workflow_dispatch:
jobs:
build:
runs-on: macos-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Update brew
run: brew update
- uses: actions/checkout@v4
- name: Install build tools
run: brew install automake autoconf libtool pkg-config
run: brew install automake autoconf libtool pkg-config gettext
- name: Install gperf, bison and flex
# MacOS comes with an ancient bison, we need a newer version. Homebrew's
# bison and flex are keg-only, which means they are not symlinked into
# /usr/local because macOS already provides this software. Homebrew tells
# you to adjust the $PATH, but I couldn't make that work, and I think
# symlinking is a better solution for simple binaries.
# macOS has ancient versions of bison and flex, so we need a newer from
# Homebrew. The new versions are installed keg-only, so we must tell
# configure/make where to look. Adjusting $PATH doesn't work (maybe
# because make invokes the two via ylwrap), so instead symlink the two
# into /usr/local/bin.
run: |
brew install gperf bison flex
sudo ln -s /usr/local/opt/bison/bin/bison /usr/local/bin/bison
sudo ln -s /usr/local/opt/flex/bin/flex /usr/local/bin/flex
sudo ln -s "$(brew --prefix)/opt/bison/bin/bison" /usr/local/bin/bison
sudo ln -s "$(brew --prefix)/opt/flex/bin/flex" /usr/local/bin/flex
- name: Install libinotify-kqueue
# brew does not have libinotify package
@ -42,40 +49,36 @@ jobs:
sudo make install
cd ..
- name: Install sqlite
# Brew package does not have unlock-notify
run: |
wget https://www.sqlite.org/2020/sqlite-autoconf-3310100.tar.gz
tar xzf sqlite-autoconf-3310100.tar.gz
cd sqlite-autoconf-3310100
export CFLAGS='-DSQLITE_ENABLE_UNLOCK_NOTIFY=1'
./configure
make
sudo make install
cd ..
- name: Install ffmpeg
# The libbluray ffmpeg dependency fails without the chown (source: stackoverflow)
run: |
sudo chown -R $(whoami) $(brew --prefix)/*
brew install ffmpeg
- name: Install other dependencies
run: brew install libunistring libmxml confuse libplist sqlite libwebsockets libevent libgcrypt json-c protobuf-c libsodium gnutls pulseaudio openssl
# libxml2 is included with macOS
run: |
brew install libunistring confuse libplist libwebsockets libevent libgcrypt json-c protobuf-c libsodium gnutls pulseaudio openssl ffmpeg sqlite
- name: Configure
# We configure a non-privileged setup, since how to add a "owntone" system
# user in macOS isn't clear to me (useradd etc. isn't available)
run: |
export ACLOCAL_PATH="$(brew --prefix)/share/gettext/m4:$ACLOCAL_PATH"
export CFLAGS="-I$(brew --prefix)/include -I$(brew --prefix sqlite)/include"
export LDFLAGS="-L$(brew --prefix)/lib -L$(brew --prefix sqlite)/lib"
autoreconf -fi
./configure --enable-chromecast --enable-lastfm --with-pulseaudio
./configure --prefix=$HOME/owntone_data/usr --sysconfdir=$HOME/owntone_data/etc --localstatedir=$HOME/owntone_data/var --enable-chromecast --with-pulseaudio
- name: Build
run: |
# Without setting these env vars the build fails with "fatal error: 'openssl/ssl.h' file not found"
# (Solution taken from https://github.com/libimobiledevice/libimobiledevice/issues/389#issuecomment-289284190)
export LD_LIBRARY_PATH=$(brew --prefix openssl)/lib
export CPATH=$(brew --prefix openssl)/include
export PKG_CONFIG_PATH=$(brew --prefix openssl)/lib/pkgconfig
make
- name: Install
run: sudo make install
run: |
make install
- name: Prepare test run
run: |
mkdir -p $HOME/owntone_data/media
sed -i '' 's/uid = "owntone"/uid = ${USER}/g' $HOME/owntone_data/etc/owntone.conf
sed -i '' 's/loglevel = log/loglevel = debug/g' $HOME/owntone_data/etc/owntone.conf
sed -i '' 's/directories = { "\/srv\/music" }/directories = { "${HOME}\/owntone_data\/media" }/g' $HOME/owntone_data/etc/owntone.conf
- name: Test run
run: |
$HOME/owntone_data/usr/sbin/owntone -f -t

69
.github/workflows/macos_12.yml vendored Normal file
View File

@ -0,0 +1,69 @@
name: macOS 12
on:
workflow_dispatch:
jobs:
build:
runs-on: macos-12
steps:
- uses: actions/checkout@v4
- name: Install build tools
run: brew install automake autoconf libtool pkg-config gettext
- name: Install gperf, bison and flex
# macOS has ancient versions of bison and flex, so we need a newer from
# Homebrew. The new versions are installed keg-only, so we must tell
# configure/make where to look. Adjusting $PATH doesn't work (maybe
# because make invokes the two via ylwrap), so instead symlink the two
# into /usr/local/bin.
run: |
brew install gperf bison flex
sudo ln -s "$(brew --prefix)/opt/bison/bin/bison" /usr/local/bin/bison
sudo ln -s "$(brew --prefix)/opt/flex/bin/flex" /usr/local/bin/flex
- name: Install libinotify-kqueue
# brew does not have libinotify package
run: |
git clone https://github.com/libinotify-kqueue/libinotify-kqueue
cd libinotify-kqueue
autoreconf -fvi
./configure
make
sudo make install
cd ..
- name: Install other dependencies
# libxml2 is included with macOS
run: |
brew install libunistring confuse libplist libwebsockets libevent libgcrypt json-c protobuf-c libsodium gnutls pulseaudio openssl ffmpeg sqlite
- name: Configure
# We configure a non-privileged setup, since how to add a "owntone" system
# user in macOS isn't clear to me (useradd etc. isn't available)
run: |
export CFLAGS="-I$(brew --prefix)/include -I$(brew --prefix sqlite)/include"
export LDFLAGS="-L$(brew --prefix)/lib -L$(brew --prefix sqlite)/lib"
autoreconf -fi
./configure --prefix=$HOME/owntone_data/usr --sysconfdir=$HOME/owntone_data/etc --localstatedir=$HOME/owntone_data/var --enable-chromecast --with-pulseaudio
- name: Build
run: |
make
- name: Install
run: |
make install
- name: Prepare test run
run: |
mkdir -p $HOME/owntone_data/media
sed -i '' 's/uid = "owntone"/uid = ${USER}/g' $HOME/owntone_data/etc/owntone.conf
sed -i '' 's/loglevel = log/loglevel = debug/g' $HOME/owntone_data/etc/owntone.conf
sed -i '' 's/directories = { "\/srv\/music" }/directories = { "${HOME}\/owntone_data\/media" }/g' $HOME/owntone_data/etc/owntone.conf
- name: Test run
run: |
$HOME/owntone_data/usr/sbin/owntone -f -t

View File

@ -2,30 +2,51 @@ name: Ubuntu
on:
push:
branches: [ master ]
branches:
- master
paths-ignore:
- 'docs/**'
- 'htdocs/**'
- 'web-src/**'
pull_request:
branches: [ master ]
branches:
- master
paths-ignore:
- 'docs/**'
- 'htdocs/**'
- 'web-src/**'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: install dependencies
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -yq build-essential clang clang-tools git autotools-dev autoconf libtool gettext gawk gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev libevent-dev libplist-dev libsodium-dev libcurl4-openssl-dev libjson-c-dev libprotobuf-c-dev libpulse-dev libwebsockets-dev libgnutls28-dev
- name: build and check
sudo apt-get install -yq build-essential clang clang-tools git autotools-dev autoconf libtool gettext gawk gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev libasound2-dev libxml2-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev libevent-dev libplist-dev libsodium-dev libcurl4-openssl-dev libjson-c-dev libprotobuf-c-dev libpulse-dev libwebsockets-dev libgnutls28-dev
- name: Build and check
run: |
autoreconf -vi
./configure
./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-install-user --enable-chromecast --with-pulseaudio
make
make check
make distcheck
- name: build with lastfm, chromecast, pulse
- name: Install
run: |
autoreconf -vi
./configure --enable-lastfm --enable-chromecast --with-pulseaudio
make
sudo mkdir -p /srv/music
sudo make install
sudo sed -i 's/loglevel = log/loglevel = debug/g' /etc/owntone.conf
- name: Install and run avahi-daemon
run: |
sudo apt-get install -yq avahi-daemon
- name: Test run
run: |
sudo /usr/sbin/owntone -f -t

27
.github/workflows/webui_lint.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Web UI Lint
on:
push:
branches:
- master
paths:
- 'web-src/**'
pull_request:
branches:
- master
paths:
- 'web-src/**'
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
working-directory: web-src
run: npm install
- name: Run linter
working-directory: web-src
run: npm run lint --no-fix

6
.gitignore vendored
View File

@ -39,7 +39,11 @@ owntone@.service
/.cproject
/.project
/.autotools
/.vscode
# ignore MkDocs generated documentation
/site/
/test/
/.vscode/
/.devcontainer/
!/.dev/Makefile

1077
ChangeLog

File diff suppressed because it is too large Load Diff

View File

@ -23,24 +23,29 @@ dist_man_MANS = owntone.8
nobase_dist_doc_DATA = \
UPGRADING \
README.md \
docs/index.md \
docs/getting-started.md \
docs/installation.md \
docs/configuration.md \
docs/building.md \
docs/library.md \
docs/control-clients/mobile.md \
docs/control-clients/desktop.md \
docs/control-clients/web.md \
docs/control-clients/cli-api.md \
docs/audio-outputs/airplay.md \
docs/audio-outputs/chromecast.md \
docs/audio-outputs/local-audio.md \
docs/audio-outputs/mobile.md \
docs/audio-outputs/web.md \
docs/audio-outputs/roku.md \
docs/audio-outputs/streaming.md \
docs/media-clients.md \
docs/artwork.md \
docs/playlists.md \
docs/smart-playlists.md \
docs/integrations/spotify.md \
docs/integrations/lastfm.md \
docs/index.md \
docs/outputs/streaming.md \
docs/outputs/chromecast.md \
docs/outputs/airplay.md \
docs/outputs/local-audio.md \
docs/installation.md \
docs/clients/web-interface.md \
docs/clients/remote.md \
docs/clients/cli.md \
docs/clients/supported-clients.md \
docs/clients/mpd.md \
docs/smart-playlists.md \
docs/artwork.md \
docs/library.md \
docs/getting-started.md \
docs/advanced/radio-streams.md \
docs/advanced/multiple-instances.md \
docs/advanced/outputs-alsa.md \

View File

@ -1,15 +1,18 @@
# OwnTone (previously forked-daapd)
# OwnTone
OwnTone is a Linux/FreeBSD DAAP (iTunes), MPD (Music Player Daemon) and
RSP (Roku) media server.
OwnTone is a media server that lets you play audio sources such as local files,
Spotify, pipe input or internet radio to AirPlay 1 and 2 receivers, Chromecast
receivers, Roku Soundbridge, a browser or the servers own sound system. Or you
can listen to your music via any client that supports mp3 streaming.
It supports AirPlay devices/speakers, Apple Remote (and compatibles),
MPD clients, Chromecast, network streaming, internet radio, Spotify and LastFM.
You control the server via a web interface, Apple Remote, an Android remote
(e.g. Retune), an MPD client, json API or DACP.
It does not support streaming video by AirPlay nor Chromecast.
OwnTone also serves local files via the Digital Audio Access Protocol (DAAP) to
iTunes (Windows), Apple Music (macOS) and Rhythmbox (Linux), and via the Roku
Server Protocol (RSP) to Roku devices.
DAAP stands for Digital Audio Access Protocol which is the protocol used
by iTunes and friends to share/stream media libraries over the network.
Runs on Linux, BSD and macOS.
OwnTone was previously called forked-daapd, which again was a rewrite of
mt-daapd (Firefly Media Server).

View File

@ -1,7 +1,7 @@
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ([2.60])
AC_INIT([owntone], [28.7])
AC_INIT([owntone], [29.0])
AC_CONFIG_SRCDIR([config.h.in])
AC_CONFIG_MACRO_DIR([m4])
@ -50,7 +50,7 @@ AC_CHECK_HEADERS([sys/wait.h sys/param.h dirent.h getopt.h stdint.h], [],
[AC_MSG_ERROR([[Missing header required to build OwnTone]])])
AC_CHECK_HEADERS([time.h], [],
[AC_MSG_ERROR([[Missing header required to build OwnTone]])])
AC_CHECK_FUNCS_ONCE([posix_fadvise pipe2 syscall])
AC_CHECK_FUNCS_ONCE([posix_fadvise pipe2 gettid])
AC_CHECK_FUNCS([strptime strtok_r], [],
[AC_MSG_ERROR([[Missing function required to build OwnTone]])])
@ -77,18 +77,35 @@ AC_SEARCH_LIBS([pthread_exit], [pthread], [],
AC_SEARCH_LIBS([pthread_setname_np], [pthread],
[dnl Validate pthread_setname_np with 2 args (some have 1)
AC_MSG_CHECKING([[for two-parameter pthread_setname_np]])
AC_TRY_LINK([@%:@include <pthread.h>],
[pthread_setname_np(pthread_self(), "name");],
AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include <pthread.h>]],
[[pthread_setname_np(pthread_self(), "name");]])],
[AC_MSG_RESULT([yes])
AC_DEFINE([HAVE_PTHREAD_SETNAME_NP], 1,
[Define to 1 if you have pthread_setname_np])],
[AC_MSG_RESULT([[no]])])],
[AC_SEARCH_LIBS([pthread_set_name_np], [pthread],
[AC_CHECK_FUNCS([pthread_set_name_np])])])
AC_SEARCH_LIBS([pthread_getname_np], [pthread],
[AC_DEFINE([HAVE_PTHREAD_GETNAME_NP], 1,
[Define to 1 if you have pthread_getname_np])]
[AC_SEARCH_LIBS([pthread_get_name_np], [pthread],
[AC_DEFINE([HAVE_PTHREAD_GETNAME_NP], 1,
[Define to 1 if you have pthread_get_name_np])])])
AC_SEARCH_LIBS([pthread_getthreadid_np], [pthread],
[AC_DEFINE([HAVE_PTHREAD_GETTHREADID_NP], 1,
[Define to 1 if you have pthread_getthreadid_np])])
AC_SEARCH_LIBS([uuid_generate_random], [uuid],
[AC_DEFINE([HAVE_UUID], 1,
[Define to 1 if you have uuid_generate_random function])])
[Define to 1 if you have uuid_generate_random])])
AC_SEARCH_LIBS([copy_file_range], [c],
[AC_DEFINE([HAVE_COPY_FILE_RANGE], 1,
[Define to 1 if you have copy_file_range])])
AC_SEARCH_LIBS([fcopyfile], [c],
[AC_DEFINE([HAVE_FCOPYFILE], 1,
[Define to 1 if you have fcopyfile])])
AC_SEARCH_LIBS([mnt_new_monitor], [mount],
[AC_DEFINE([HAVE_LIBMOUNT], 1,
[Define to 1 if you have libmount])])
AC_SEARCH_LIBS([log10], [m])
AC_SEARCH_LIBS([lrint], [m])
@ -120,13 +137,7 @@ OWNTONE_MODULES_CHECK([OWNTONE], [ZLIB], [zlib], [deflate], [zlib.h])
OWNTONE_MODULES_CHECK([OWNTONE], [CONFUSE], [libconfuse >= 3.0], [cfg_init], [confuse.h])
OWNTONE_MODULES_CHECK([OWNTONE], [LIBCURL], [libcurl], [curl_global_init], [curl/curl.h])
OWNTONE_MODULES_CHECK([OWNTONE], [LIBSODIUM], [libsodium], [sodium_init], [sodium.h])
OWNTONE_MODULES_CHECK([OWNTONE], [MINIXML], [mxml],
[mxmlNewElement], [mxml.h],
[
dnl See mxml-compat.h
AC_CHECK_FUNCS([mxmlGetOpaque] [mxmlGetText] [mxmlGetType] [mxmlGetFirstChild])
])
OWNTONE_MODULES_CHECK([OWNTONE], [LIBXML2], [libxml-2.0], [xmlInitParser], [libxml/parser.h])
OWNTONE_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0],
[sqlite3_initialize], [sqlite3.h],
@ -149,7 +160,13 @@ OWNTONE_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0],
])
OWNTONE_MODULES_CHECK([OWNTONE], [LIBEVENT], [libevent >= 2.1.4],
[event_base_new], [event2/event.h])
[event_base_new], [event2/event.h],
[dnl check for version 2.2 (with websocket server support)
PKG_CHECK_EXISTS([libevent >= 2.2.1],
[AC_DEFINE([HAVE_LIBEVENT22], 1,
[Define to 1 if you have libevent > 2.2])],
[])
])
OWNTONE_MODULES_CHECK([OWNTONE], [LIBEVENT_PTHREADS], [libevent_pthreads],
[evthread_use_pthreads], [event2/thread.h])
@ -178,6 +195,9 @@ PKG_CHECK_EXISTS([libplist],
[OWNTONE_MODULES_CHECK([OWNTONE], [LIBPLIST], [libplist-2.0],
[plist_dict_get_item], [plist/plist.h])])
dnl AC_SEARCH_LIBS does not find plist_get_unix_date_val() on MacOS
OWNTONE_CHECK_DECLS([plist_get_unix_date_val], [plist/plist.h])
AM_PATH_LIBGCRYPT([1:1.7.0])
OWNTONE_FUNC_REQUIRE([OWNTONE], [GNU Crypt Library], [LIBGCRYPT], [gcrypt],
[gcry_control], [gcrypt.h])
@ -245,6 +265,8 @@ OWNTONE_MODULES_CHECK([OWNTONE], [LIBAV],
[libavutil/avutil.h])
OWNTONE_CHECK_DECLS([avformat_network_init],
[libavformat/avformat.h])
OWNTONE_CHECK_DECLS([av_dict_iterate],
[libavutil/dict.h])
])
AC_CHECK_SIZEOF([void *])

View File

@ -1,6 +1,6 @@
# Running Multiple Instances
To run multiple instances of owntone on a server, you should copy
To run multiple instances of OwnTone on a server, you should copy
`/etc/owntone.conf` to `/etc/owntone-zone.conf` (for each `zone`) and
modify the following to be unique across all instances:
@ -17,9 +17,8 @@ modify the following to be unique across all instances:
Then run `owntone -c /etc/owntone-zone.conf` to run owntone with the new
zone configuration.
Owntone has a `systemd` template which lets you run this automatically
OwnTone has a `systemd` template which lets you run this automatically
on systems that use systemd. You can start or enable the service for
a `zone` by `sudo systemctl start owntone@zone` and check that it is
running with `sudo systemctl status owntone@zone`. Use `sudo
systemctl enable ownton@zone` to get the service to start on reboot.
systemctl enable owntone@zone` to get the service to start on reboot.

View File

@ -1,6 +1,6 @@
# OwnTone and ALSA
# ALSA
ALSA is one of the main output configuration options for local audio; when using ALSA you will typically let the system select the soundcard on your machine as the `default` device/sound card - a mixer associated with the ALSA device is used for volume control. However if your machine has multiple sound cards and your system chooses the wrong playback device, you will need to manually select the card and mixer to complete the OwnTone configuration.
ALSA is one of the main output configuration options for local audio; when using ALSA you will typically let the system select the sound card on your machine as the `default` device/sound card - a mixer associated with the ALSA device is used for volume control. However if your machine has multiple sound cards and your system chooses the wrong playback device, you will need to manually select the card and mixer to complete the OwnTone configuration.
## Quick introduction to ALSA devices
@ -13,13 +13,14 @@ Alternative ALSA names can be used to refer to physical ALSA devices and can be
* more descriptive rather than being a card number
* consistent for USB numeration - USB ALSA devices may not have the same card number across reboots/reconnects
The ALSA device information required for configuration the server can be deterined using `aplay`, as described in the rest of this document, but OwnTone can also assist; when configured to log at `INFO` level the following information is provided during startup:
The ALSA device information required for configuration the server can be determined using `aplay`, as described in the rest of this document, but OwnTone can also assist; when configured to log at `INFO` level the following information is provided during startup:
```
```shell
laudio: Available ALSA playback mixer(s) on hw:0 CARD=Intel (HDA Intel): 'Master' 'Headphone' 'Speaker' 'PCM' 'Mic' 'Beep'
laudio: Available ALSA playback mixer(s) on hw:1 CARD=E30 (E30): 'E30 '
laudio: Available ALSA playback mixer(s) on hw:2 CARD=Seri (Plantronics Blackwire 3210 Seri): 'Sidetone' 'Headset'
```
The `CARD=` string is the alternate ALSA name for the device and can be used in place of the traditional `hw:x` name.
On this machine the server reports that it can see the onboard HDA Intel sound card and two additional sound cards: a Topping E30 DAC and a Plantronics Headset which are both USB devices. We can address the first ALSA device as `hw:0` or `hw:CARD=Intel` or `hw:Intel` or `plughw:Intel`, the second ALSA device as `hw:1` or `hw:E30` and so forth. The latter 2 devices being on USB will mean that `hw:1` may not always refer to `hw:E30` and thus in such a case using the alternate name is useful.
@ -28,8 +29,8 @@ On this machine the server reports that it can see the onboard HDA Intel sound c
OwnTone can support a single ALSA device or multiple ALSA devices.
```
# example audio section for server for a single soundcard
```conf
# example audio section for server for a single sound card
audio {
nickname = "Computer"
type = "alsa"
@ -40,9 +41,9 @@ audio {
}
```
Multiple devices can be made available to OwnTone using seperate `alsa { .. }` sections.
Multiple devices can be made available to OwnTone using separate `alsa { .. }` sections.
```
```conf
audio {
type = "alsa"
}
@ -62,11 +63,11 @@ NB: When introducing `alsa { .. }` section(s) the ALSA specific configuration in
If there is only one sound card, verify if the `default` sound device is correct for playback, we will use the `aplay` utility.
```
```shell
# generate some audio if you don't have a wav file to hand
$ sox -n -c 2 -r 44100 -b 16 -C 128 /tmp/sine441.wav synth 30 sin 500-100 fade h 0.2 30 0.2
sox -n -c 2 -r 44100 -b 16 -C 128 /tmp/sine441.wav synth 30 sin 500-100 fade h 0.2 30 0.2
$ aplay -Ddefault /tmp/sine441.wav
aplay -Ddefault /tmp/sine441.wav
```
If you can hear music played then you are good to use `default` for the server configuration. If you can not hear anything from the `aplay` firstly verify (using `alsamixer`) that the sound card is not muted. If the card is not muted AND there is no sound you can try the options below to determine the card and mixer for configuring the server.
@ -75,15 +76,15 @@ If you can hear music played then you are good to use `default` for the server c
As shown above, OwnTone can help, consider the information that logged:
```
```log
laudio: Available ALSA playback mixer(s) on hw:0 CARD=Intel (HDA Intel): 'Master' 'Headphone' 'Speaker' 'PCM' 'Mic' 'Beep'
laudio: Available ALSA playback mixer(s) on hw:1 CARD=E30 (E30): 'E30 '
laudio: Available ALSA playback mixer(s) on hw:2 CARD=Seri (Plantronics Blackwire 3210 Seri): 'Sidetone' 'Headset'
```
Using the information above, we can see 3 soundcards that we could use with OwnTone with the first soundcard having a number of seperate mixer devices (volume control) for headphone and the interal speakers - we'll configure the server to use both these and also the E30 device. The server configuration for theese multiple outputs would be:
Using the information above, we can see 3 sound cards that we could use with OwnTone with the first sound card having a number of separate mixer devices (volume control) for headphone and the internal speakers - we'll configure the server to use both these and also the E30 device. The server configuration for these multiple outputs would be:
```
```conf
# using ALSA device alias where possible
alsa "hw:Intel" {
@ -109,12 +110,14 @@ alsa "plughw:E30" {
NB: it is troublesome to use `hw` or `plughw` ALSA addressing when running OwnTone on a machine with `pulseaudio` and if you wish to use refer to ALSA devices directly that you stop `pulseaudio`.
## Manually Determining the sound cards you have / ALSA can see
The example below is how I determined the correct sound card and mixer values for a Raspberry Pi that has an additional DAC card (hat) mounted. Of course using the log output from the server would have given the same results.
Use `aplay -l` to list all the sound cards and their order as known to the system - you can have multiple `card X, device Y` entries; some cards can also have multiple playback devices such as the RPI's onboard soundcard which feeds both headphone (card 0, device 0) and HDMI (card 0, device 1).
Use `aplay -l` to list all the sound cards and their order as known to the system - you can have multiple `card X, device Y` entries; some cards can also have multiple playback devices such as the RPI's onboard sound card which feeds both headphone (card 0, device 0) and HDMI (card 0, device 1).
```
```shell
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
Subdevices: 6/7
@ -135,12 +138,13 @@ card 1: IQaudIODAC [IQaudIODAC], device 0: IQaudIO DAC HiFi pcm512x-hifi-0 []
On this machine we see the second sound card installed, an IQaudIODAC dac hat, and identified as `card 1 device 0`. This is the playback device we want to be used by the server.
`hw:1,0` is the IQaudIODAC that we want to use - we verify audiable playback through that sound card using `aplay -Dhw:1 /tmp/sine441.wav`. If the card has only one device, we can simply refer to the sound card using `hw:X` so in this case where the IQaudIODAC only has one device, we can refer to this card as `hw:1` or `hw:1,0`.
`hw:1,0` is the IQaudIODAC that we want to use - we verify audible playback through that sound card using `aplay -Dhw:1 /tmp/sine441.wav`. If the card has only one device, we can simply refer to the sound card using `hw:X` so in this case where the IQaudIODAC only has one device, we can refer to this card as `hw:1` or `hw:1,0`.
Use `aplay -L` to get more information about the PCM devices defined on the system.
```
```shell
$ aplay -L
null
Discard all samples (playback) or generate zero samples (capture)
default:CARD=ALSA
@ -195,7 +199,7 @@ plughw:CARD=IQaudIODAC,DEV=0
For the server configuration, we will use:
```
```conf
audio {
nickname = "Computer"
type = "alsa"
@ -209,7 +213,7 @@ audio {
Once you have the card number (determined from `aplay -l`) we can inspect/confirm the name of the mixer that can be used for playback (it may NOT be `PCM` as expected by the server). In this example, the card `1` is of interest and thus we use `-c 1` with the following command:
```
```shell
$ amixer -c 1
Simple mixer control 'DSP Program',0
Capabilities: enum
@ -236,7 +240,7 @@ This card has multiple controls but we want to find a mixer control listed with
For the server configuration, we will use:
```
```conf
audio {
nickname = "Computer"
type = "alsa"
@ -250,14 +254,13 @@ audio {
This is the name of the underlying physical device used for the mixer - it is typically the same value as the value of `card` in which case a value is not required by the server configuration. An example of when you want to change explicitly configure this is if you need to use a `dmix` device (see below).
## Handling Devices that cannot concurrently play multiple audio streams
## Handling Devices that cannot concurrently play multiple audio streams
Some devices such as various RPI DAC boards (IQaudio DAC, Allo Boss DAC...) cannot have multiple streams openned at the same time/cannot play multiple sound files at the same time. This results in `Device or resource busy` errors. You can confirm if your sound card has this problem by using the example below once have determined the names/cards information as above.
Some devices such as various RPI DAC boards (IQaudio DAC, Allo Boss DAC...) cannot have multiple streams opened at the same time/cannot play multiple sound files at the same time. This results in `Device or resource busy` errors. You can confirm if your sound card has this problem by using the example below once have determined the names/cards information as above.
Using our `hw:1` device we try:
```
```shell
# generate some audio
$ sox -n -c 2 -r 44100 -b 16 -C 128 /tmp/sine441.wav synth 30 sin 500-100 fade h 0.2 30 0.2
@ -295,13 +298,13 @@ aplay: main:788: audio open error: Device or resource busy
In this instance this device cannot open multiple streams - OwnTone can handle this situation transparently with some audio being truncated from the end of the current file as the server prepares to play the following track. If this handling is causing you problems you may wish to use [ALSA's `dmix` functionally](https://www.alsa-project.org/main/index.php/Asoundrc#Software_mixing) which provides a software mixing module. We will need to define a `dmix` component and configure the server to use that as it's sound card.
The downside to the `dmix` approach will be the need to fix a samplerate (48000 being the default) for this software mixing module meaning any files that have a mismatched samplerate will be resampled.
The downside to the `dmix` approach will be the need to fix a sample rate (48000 being the default) for this software mixing module meaning any files that have a mismatched sample rate will be resampled.
## ALSA dmix configuration/setup
A `dmix` device can be defined in `/etc/asound.conf` or `~/.asoundrc` for the same user running OwnTone. We will need to know the underlying physical soundcard to be used: in our examples above, `hw:1,0` / `card 1, device 0` representing our IQaudIODAC as per output of `aplay -l`. We also take the `buffer_size` and `period_size` from the output of playing a sound file via `aplay -v`.
A `dmix` device can be defined in `/etc/asound.conf` or `~/.asoundrc` for the same user running OwnTone. We will need to know the underlying physical sound card to be used: in our examples above, `hw:1,0` / `card 1, device 0` representing our IQaudIODAC as per output of `aplay -l`. We also take the `buffer_size` and `period_size` from the output of playing a sound file via `aplay -v`.
```
```conf
# use 'dac' as the name of the device: "aplay -Ddac ...."
pcm.!dac {
type plug
@ -316,11 +319,11 @@ pcm.dmixer {
ipc_perm 0666 # multi-user sharing permissions
slave {
pcm "hw:1,0" # points at the underlying device - could also simply be hw:1
period_time 0
period_size 4096 # from the output of aplay -v
buffer_size 22052 # from the output of aplay -v
rate 44100 # locked in sample rate for resampling on dmix device
pcm "hw:1,0" # points at the underlying device - could also simply be hw:1
period_time 0
period_size 4096 # from the output of aplay -v
buffer_size 22052 # from the output of aplay -v
rate 44100 # locked in sample rate for resampling on dmix device
}
hint.description "IQAudio DAC s/w dmix device"
}
@ -335,7 +338,7 @@ ctl.dmixer {
Running `aplay -L` we will see our newly defined devices `dac` and `dmixer`
```
```shell
$ aplay -L
null
Discard all samples (playback) or generate zero samples (capture)
@ -359,7 +362,7 @@ We will use the newly defined card named `dac` which uses the underlying `hw:1`
For the final server configuration, we will use:
```
```conf
audio {
nickname = "Computer"
type = "alsa"
@ -377,7 +380,7 @@ Once installed the user must setup a virtual device and use this device in the s
If you wish to use your `hw:0` device for output:
```
```conf
# /etc/asound.conf
ctl.equal {
type equal;
@ -401,7 +404,7 @@ pcm.equal {
and in `owntone.conf`
```
```conf
alsa "equal" {
nickname = "Equalised Output"
# adjust accordingly for mixer with pvolume capability
@ -423,7 +426,7 @@ Note however, the equalizer appears to require a `plughw` device which means you
`mixer` value is wrong. Verify name of `mixer` value in server config against the names from all devices capable of playback using `amixer -c <card number>`. Assume the device is card 1:
```
```shell
(IFS=$'\n'
CARD=1
for i in $(amixer -c ${CARD} scontrols | awk -F\' '{ print $2 }'); do
@ -432,9 +435,9 @@ Note however, the equalizer appears to require a `plughw` device which means you
)
```
Look at the names output and choose the one that fits. The outputs can be something like:
Look at the names output and choose the one that fits. The outputs can be something like:
```
```shell
# laptop
Master
Headphone
@ -453,16 +456,16 @@ Note however, the equalizer appears to require a `plughw` device which means you
* No sound during playback - valid mixer/verified by aplay
Check that the mixer is not muted or volume set to 0. Using the value of `mixer` as per server config and unmute or set volume to max. Assume the device is card 1 and `mixer = Analogue`:
Check that the mixer is not muted or volume set to 0. Using the value of `mixer` as per server config and unmute or set volume to max. Assume the device is card 1 and `mixer = Analogue`:
```
```shell
amixer -c 1 set Analogue unmute ## some mixers can not be muted resulting in "invalid command"
amixer -c 1 set Analogue 100%
```
An example of a device with volume turned all the way down - notice the `Playback` values are `0`[0%]`:
```
```shell
Simple mixer control 'Analogue',0
Capabilities: pvolume
Playback channels: Front Left - Front Right
@ -475,7 +478,7 @@ Note however, the equalizer appears to require a `plughw` device which means you
* Server stops playing after moving to new track in paly queue, Error in log `Could not open playback device`
The log contains these log lines:
```
```log
[2019-06-19 20:52:51] [ LOG] laudio: open '/dev/snd/pcmC0D0p' failed (-16)[2019-06-19 20:52:51] [ LOG] laudio: Could not open playback device: Device or resource busy
[2019-06-19 20:52:51] [ LOG] laudio: Device 'hw' does not support quality (48000/16/2), falling back to default
[2019-06-19 20:52:51] [ LOG] laudio: open '/dev/snd/pcmC0D0p' failed (-16)[2019-06-19 20:52:51] [ LOG] laudio: Could not open playback device: Device or resource busy
@ -487,3 +490,7 @@ Note however, the equalizer appears to require a `plughw` device which means you
This error will occur for output hardware that do not support concurrent device open and the server plays 2 files of different bitrate (44.1khz and 48khz) back to back.
If you observe the error, you will need to use the `dmix` configuration as mentioned above.
* Volume control on Raspberry Pi with hdmi output doesn't work
Prior to Debian 13, in the /boot/firmware.config file the entry "dtoverlay=vc4-kms-v3d" was commented out. Since 13 this line is active (not commented out). This changes, among other things, the way the HDMI and BCM devices are handled. Commenting out the entry again will fix the volume control for the hdmi output. If you use the RPi headless commenting this out shouldn't have undesirable effects. See https://forums.raspberrypi.com/viewtopic.php?t=49928&start=1825#p2344272.

View File

@ -1,19 +1,18 @@
# OwnTone and Pulseaudio
# PulseAudio
You have the choice of running Pulseaudio either in system mode or user mode.
You have the choice of running PulseAudio either in system mode or user mode.
For headless servers, i.e. systems without desktop users, system mode is
recommended.
If there is a desktop user logged in most of the time, a setup with network
access via localhost only for daemons is a more appropriate solution, since the
normal user administration (with, e.g., `pulseaudio -k`) works as advertised.
Also, the user specific configuration for pulseaudio is preserved across
Also, the user specific configuration for PulseAudio is preserved across
sessions as expected.
- [System mode](#system-mode-with-bluetooth-support)
- [User mode](#user-mode-with-network-access)
## System Mode with Bluetooth support
Credit: [Rob Pope](http://robpope.co.uk/blog/post/setting-up-forked-daapd-with-bluetooth)
@ -21,22 +20,21 @@ Credit: [Rob Pope](http://robpope.co.uk/blog/post/setting-up-forked-daapd-with-b
This guide was written based on headless Debian Jessie platforms. Most of the
instructions will require that you are root.
### Step 1: Setting up Pulseaudio
### Step 1: Setting up PulseAudio
If you see a "Connection refused" error when starting the server, then you
will probably need to setup Pulseaudio to run in system mode [1]. This means
that the Pulseaudio daemon will be started during boot and be available to all
will probably need to setup PulseAudio to run in system mode [1]. This means
that the PulseAudio daemon will be started during boot and be available to all
users.
How to start Pulseaudio depends on your distribution, but in many cases you will
need to add a pulseaudio.service file to /etc/systemd/system with the following
content:
How to start PulseAudio depends on your distribution, but in many cases you will
need to add a `pulseaudio.service` file to `/etc/systemd/system` with the
following content:
```
# systemd service file for Pulseaudio running in system mode
```conf
# systemd service file for PulseAudio running in system mode
[Unit]
Description=Pulseaudio sound server
Description=PulseAudio sound server
Before=sound.target
[Service]
@ -46,44 +44,42 @@ ExecStart=/usr/bin/pulseaudio --system --disallow-exit
WantedBy=multi-user.target
```
If you want Bluetooth support, you must also configure Pulseaudio to load the
If you want Bluetooth support, you must also configure PulseAudio to load the
Bluetooth module. First install it (Debian:
`apt install pulseaudio-module-bluetooth`) and then add the following to
/etc/pulse/system.pa:
```
```conf
#### Enable Bluetooth
.ifexists module-bluetooth-discover.so
load-module module-bluetooth-discover
.endif
```
Now you need to make sure that Pulseaudio can communicate with the Bluetooth
Now you need to make sure that PulseAudio can communicate with the Bluetooth
daemon through D-Bus. On Raspbian this is already enabled, and you can skip this
step. Otherwise do one of the following:
1. Add the pulse user to the bluetooth group: `adduser pulse bluetooth`
2. Edit /etc/dbus-1/system.d/bluetooth.conf and change the policy for `<policy context="default"\>` to "allow"
Phew, almost done with Pulseaudio! Now you should:
Phew, almost done with PulseAudio! Now you should:
1. enable system mode on boot with `systemctl enable pulseaudio`
2. reboot (or at least restart dbus and pulseaudio)
3. check that the Bluetooth module is loaded with `pactl list modules short`
### Step 2: Setting up the server
Add the user the server is running as (typically "owntone") to the
"pulse-access" group:
```
```shell
adduser owntone pulse-access
```
Now (re)start the server.
### Step 3: Adding a Bluetooth device
To connect with the device, run `bluetoothctl` and then:
@ -99,51 +95,45 @@ trust [MAC address]
connect [MAC address]
```
Now the speaker should appear. You can also verify that Pulseaudio has detected
Now the speaker should appear. You can also verify that PulseAudio has detected
the speaker with `pactl list sinks short`.
## User Mode with Network Access
Credit: wolfmanx and [this blog](http://billauer.co.il/blog/2014/01/pa-multiple-users/)
### Step 1: Copy system pulseaudio configuration to the users home directory
```
```shell
mkdir -p ~/.pulse
cp /etc/pulse/default.pa ~/.pulse/
```
### Step 2: Enable TCP access from localhost only
Edit the file `~/.pulse/default.pa` , adding the following line at the end:
```
```shell
load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1
```
### Step 3: Restart the PulseAudio daemon
### Step 3: Restart the pulseaudio deamon
```
```shell
pulseaudio -k
# OR
pulseaudio -D
```
### Step 4: Adjust configuration file
### Step 4: Adjust the Configuration File
In the `audio` section of `/etc/owntone.conf`, set `server` to `localhost`:
```
```conf
server = "localhost"
```
---
[1] Note that Pulseaudio will warn against system mode. However, in this use
case it is actually the solution recommended by the [Pulseaudio folks themselves](https://lists.freedesktop.org/archives/pulseaudio-discuss/2016-August/026823.html).
[1] Note that PulseAudio will warn against system mode. However, in this use
case it is actually the solution recommended by the [PulseAudio folks themselves](https://lists.freedesktop.org/archives/pulseaudio-discuss/2016-August/026823.html).

View File

@ -1,4 +1,4 @@
# OwnTone and Radio Stream tweaking
# Radio Streams
Radio streams have many different ways in how metadata is sent. Many should
just work as expected, but a few may require some tweaking. If you are not
@ -6,11 +6,11 @@ seeing expected title, track, artist, artwork in clients or web UI, the
following may help.
First, understand what and how the particular stream is sending information.
ffprobe is a command that can be used to interegrate most of the stream
`ffprobe` is a command that can be used to integrate most of the stream
information. `ffprobe <http://stream.url>` should give you some useful output,
look at the Metadata section, below is an example.
```
```m3u
Metadata:
icy-br : 320
icy-description : DJ-mixed blend of modern and classic rock, electronica, world music, and more. Always 100% commercial-free
@ -26,11 +26,10 @@ In the example above, all tags are populated with correct information, no
modifications to the server configuration should be needed. Note that
StreamUrl points to the artwork image file.
Below is another example that will require some tweaks to the server, Notice
`icy-name` is blank and `StreamUrl` doesn't point to an image.
```
```m3u
Metadata:
icy-br : 127
icy-pub : 0
@ -44,10 +43,11 @@ Metadata:
In the above, first fix is the blank name, second is the image artwork.
### 1) Set stream name/title via the M3U file
## 1) Set stream name/title via the M3U file
Set the name with an EXTINF tag in the m3u playlist file:
```
```m3u
#EXTM3U
#EXTINF:-1, - My Radio Stream Name
http://radio.stream.domain/stream.url
@ -58,7 +58,8 @@ Length is -1 since it's a stream, `<Artist Name>` was left blank since
`StreamTitle` is accurate in the Metadata but `<Artist Title>` was set to
`My Radio Stream Name` since `icy-name` was blank.
### 2) StreamUrl is a JSON file with metadata
## 2) StreamUrl is a JSON file with metadata
If `StreamUrl` does not point directly to an artwork file then the link may be
to a json file that contains an artwork link. If so, you can make the server
download the file automatically and search for an artwork link, and also track
@ -67,7 +68,7 @@ duration.
Try to download the file, e.g. with `curl "https://radio.stream.domain/api9/eventdata/49790578"`.
Let's assume you get something like this:
```
```json
{
"eventId": 49793707,
"eventStart": "2020-05-08 16:23:03",
@ -85,14 +86,15 @@ Let's assume you get something like this:
In this case, you would need to tell the server to look for "eventDuration"
and "eventImageUrl" (or just "duration" and "url"). You can do that like this:
```
```shell
curl -X PUT "http://localhost:3689/api/settings/misc/streamurl_keywords_length" --data "{\"name\":\"streamurl_keywords_length\",\"value\":\"duration\"}"
curl -X PUT "http://localhost:3689/api/settings/misc/streamurl_keywords_artwork_url" --data "{\"name\":\"streamurl_keywords_artwork_url\",\"value\":\"url\"}
```
If you want multiple search phrases then comma separate, e.g. "duration,length".
### 3) Set metadata with a custom script
## 3) Set metadata with a custom script
If your radio station publishes metadata via another method than the above, e.g.
just on their web site, then you will have to write a script that pulls the
metadata and then pushes it to the server. To update metadata for the

View File

@ -1,14 +1,14 @@
# Remote access
# Remote Access
It is possible to access a shared library over the internet from a DAAP client
like iTunes. You must have remote access to the host machine.
First log in to the host and forward port 3689 to your local machine. You now
need to broadcast the daap service to iTunes on your local machine. On macOS the
need to broadcast the DAAP service to iTunes on your local machine. On macOS the
command is:
```
dns-sd -P iTunesServer _daap._tcp local 3689 localhost.local 127.0.0.1 "ffid=12345"
```shell
dns-sd -P iTunesServer _daap._tcp local 3689 localhost.local 127.0.0.1 "txtvers=1" "ffid=12345678" "Database ID=0123456789abcdef" "Machine ID=0123456789abcdef" "Machine Name=owntone" "mtd-version=28.10" "iTSh Version=131073" "Version=196610"
```
The `ffid` key is required but its value does not matter.
@ -19,3 +19,10 @@ You can also access your library remotely using something like Zerotier. See [th
guide](https://github.com/owntone/owntone-server/wiki/Accessing-Owntone-remotely-through-iTunes-Music-with-Zerotier)
for details.
## Accessing from Internet for authenticated users
If you intend to access OwnTone directly from Internet, it is recommended to
protect it against unauthenticated users.
[This guide](https://blog.cyril.by/en/software/example-sso-with-authelia-and-owntone)
has a detailed setup tutorial to achieve this securely.

View File

@ -15,7 +15,7 @@ artwork (group artwork) by the following procedure:
- failing that, if [directory name].{png,jpg} is found in one of the
directories containing files that are part of the group, it is used as the
artwork. The first file found is used, ordering is not guaranteed;
- failing that, individual files are examined and the first file found
- failing that, individual files are examined and the first file found
with an embedded artwork is used. Here again, ordering is not guaranteed.
{artwork,cover,Folder} are the default, you can add other base names in the
@ -28,7 +28,7 @@ the list, OwnTone will look for /foo/bar.{jpg,png}.
You can use symlinks for the artwork files.
OwnTone caches artwork in a separate cache file. The default path is
`/var/cache/owntone/cache.db` and can be configured in the configuration
file. The cache.db file can be deleted without losing the library and pairing
OwnTone caches artwork in a separate cache file. The default path is
`/var/cache/owntone/cache.db` and can be configured in the configuration
file. The cache.db file can be deleted without losing the library and pairing
informations.

View File

@ -11,8 +11,7 @@ interface: Select the device and then enter the PIN that the Apple TV displays.
If your speaker is silent when you start playback, and there is no obvious error
message in the log, you can try disabling ipv6 in the config. Some speakers
announce that they support ipv6, but in fact don't (at least not with forked-
daapd).
announce that they support ipv6, but for some reason don't work with OwnTone.
If the speaker becomes unselected when you start playback, and you in the log
see "ANNOUNCE request failed in session startup: 400 Bad Request", then try

View File

@ -2,3 +2,8 @@
OwnTone will discover Chromecast devices available on your network, and you
can then select the device as a speaker. There is no configuration required.
Take note that:
- Chromecast playback can't be precisely sync'ed with other outputs e.g. AirPlay
- Playback to Google Nest Hub doesn't work (Nest Mini does work)

View File

@ -0,0 +1,24 @@
# Local audio
## Local audio through ALSA
In the config file, you can select ALSA for local audio. This is the default.
When using ALSA, the server will try to synchronize playback with AirPlay. You
can adjust the synchronization in the config file.
For most setups the default values in the config file should work. If they
don't, there is help [here](../advanced/outputs-alsa.md)
## Local audio, Bluetooth and more through PulseAudio
In the config file, you can select PulseAudio for local audio. In addition to
local audio, PulseAudio also supports an array of other targets, e.g. Bluetooth
or DLNA. However, PulseAudio does require some setup, so here is a separate page
with some help on that: [PulseAudio](../advanced/outputs-pulse.md)
Note that if you select PulseAudio the "card" setting in the config file has
no effect. Instead all sound cards detected by PulseAudio will be listed as
speakers by OwnTone.
You can adjust the latency of PulseAudio playback in the config file.

View File

@ -0,0 +1,19 @@
# Listening on your Mobile Device
## iOS
On iOS, the options are limited because there are no Media Client apps with DAAP
support and because Apple doesn't allow AirPlay receiver apps. OwnTone also
can't share music via Home Sharing, which is the protocol Apple uses for sharing
media between devices.
That leaves the following options, which all rely on OwnTone's streaming:
- listen via the [web interface](web.md)
- use a [MPD client app](../control-clients/mobile.md#mpd-client-apps) that supports local playback
- connect to the [streaming](streaming.md) endpoint with a media player like VLC
## Android
On Android, you can use the same streaming methods described for iOS, but you
can also find apps that act as AirPlay receivers.

View File

@ -0,0 +1,9 @@
# Roku devices/speakers
OwnTone can stream audio to classic RSP/RCP-based devices like Roku Soundbridge
M1001 and M2000.
If the source file is in a non-supported format, like flac, OwnTone will
transcode to wav. Transmitting wav requires some bandwidth and the legacy
network interfaces of these devices may struggle with that. If so, you can
change the transcoding format for the speaker to alac via the [JSON API](../json-api.md#change-an-output).

View File

@ -0,0 +1,33 @@
# Streaming
The streaming option is useful when you want to listen to audio played by
OwnTone in a browser or a media player of your choice [^1],[^2].
You can control playback via the web interface or any of the supported control
clients.
## Listening to Audio in a Media Player
To listen to audio being played by OwnTone in a media player, follow these
steps:
1. Start playing audio in OwnTone.
2. In the web interface, activate the stream in the output menu by clicking
on the icon :material-broadcast: next to HTTP Stream.
After a few seconds, the audio should play in the background.
3. Copy the URL behind the :material-open-in-new: icon next to HTTP Stream.
4. Open the copied URL with the media player, e.g., VLC.
The URL is usually
[http://owntone.local:3689/stream.mp3](http://owntone.local:3689/stream.mp3)
or http://SERVER_ADDRESS:3689/stream.mp3
## Notes
[^1]: On iOS devices, the streaming option is the only way of listening to your
audio, since Apple does not allow AirPlay receiver apps, and because
Home Sharing cannot be supported by OwnTone.
[^2]: For the streaming option to work, MP3 encoding must be supported by
`libavcodec`. If it is not, a message will appear in the log file.
For example, on Debian or Ubuntu, MP3 encoding support is provided by the
package `libavcodec-extra`.

19
docs/audio-outputs/web.md Normal file
View File

@ -0,0 +1,19 @@
# Listening to Audio in a Browser
To listen to audio being played by OwnTone in a browser, follow these
steps:
1. Start playing audio in OwnTone.
2. In the web interface, activate the stream in the output menu by clicking
on the icon :material-broadcast: next to HTTP Stream.
After a few seconds, the audio should play in the background [^1].
![Outputs](../assets/images/screenshot-outputs.png){: class="zoom" }
For the streaming option to work, MP3 encoding must be supported by
`libavcodec`. If it is not, a message will appear in the log file. For example,
on Debian or Ubuntu, MP3 encoding support is provided by the package
`libavcodec-extra`.
[^1]: On iOS devices, playing audio in the background when the device is locked
is not supported in a private browser tab.

View File

@ -1,11 +1,11 @@
# Build instructions for OwnTone
# Build Instructions
This document contains instructions for building OwnTone from the git tree. If
you just want to build from a release tarball, you don't need the build tools
(git, autotools, autoconf, automake, gawk, gperf, gettext, bison and flex), and
you can skip the autoreconf step.
## Quick version for Debian/Ubuntu users
## Quick Version for Debian/Ubuntu
If you are the lucky kind, this should get you all the required tools and
libraries:
@ -15,7 +15,7 @@ sudo apt-get install \
build-essential git autotools-dev autoconf automake libtool gettext gawk \
gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev \
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev \
libasound2-dev libxml2-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev \
libevent-dev libplist-dev libsodium-dev libjson-c-dev libwebsockets-dev \
libcurl4-openssl-dev libprotobuf-c-dev
```
@ -29,7 +29,7 @@ argument when you run ./configure:
Feature | Configure argument | Packages
---------------------|--------------------------|-------------------------------------
Chromecast | `--enable-chromecast` | libgnutls*-dev
Pulseaudio | `--with-pulseaudio` | libpulse-dev
PulseAudio | `--with-pulseaudio` | libpulse-dev
These features can be disabled saving you package dependencies:
@ -73,7 +73,7 @@ will need ffmpeg. You can google how to do that. Then run:
```bash
sudo dnf install \
git automake autoconf gettext-devel gperf gawk libtool bison flex \
sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \
sqlite-devel libconfuse-devel libunistring-devel libxml2-devel libevent-devel \
avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel \
libplist-devel libsodium-devel json-c-devel libwebsockets-devel \
libcurl-devel protobuf-c-devel
@ -110,38 +110,37 @@ running with `sudo systemctl status owntone`.
See the [Documentation](getting-started.md) for usage information.
## Quick version for FreeBSD
## Quick Version for FreeBSD
There is a script in the 'scripts' folder that will at least attempt to do all
the work for you. And should the script not work for you, you can still look
through it and use it as an installation guide.
## Quick version for macOS (using Homebrew)
## Quick Version for macOS Using Homebrew
This workflow file used for building OwnTone via Github actions includes
all the steps that you need to execute:
[.github/workflows/macos.yml](https://github.com/owntone/owntone-server/blob/master/.github/workflows/macos.yml)
## "Quick" version for macOS (using macports)
## "Quick" Version for macOS Using MacPorts
Caution:
1) this approach may be out of date, consider using the Homebrew method above
since it is continuously tested.
2) macports requires many downloads and lots of time to install (and sometimes
build) ports... you'll want a decent network connection and some patience!
2) MacPorts requires many downloads and lots of time to install (and sometimes
build) ports. You will need a decent network connection and some patience!
Install macports (which requires Xcode): <https://www.macports.org/install.php>
Install MacPorts (which requires Xcode): <https://www.macports.org/install.php>
```bash
sudo port install \
autoconf automake libtool pkgconfig git gperf bison flex libgcrypt \
libunistring libconfuse ffmpeg libevent json-c libwebsockets curl \
libplist libsodium protobuf-c
libplist libsodium protobuf-c libxml2
```
Download, configure, build and install the Mini-XML library: <http://www.msweet.org/projects.php/Mini-XML>
Download, configure, build and install the libinotify library: <https://github.com/libinotify-kqueue/libinotify-kqueue>
Download, configure, build and install the [libinotify-kqueue library](https://github.com/libinotify-kqueue/libinotify-kqueue)
Add the following to `.bashrc`:
@ -158,16 +157,16 @@ Optional features require the following additional ports:
Feature | Configure argument | Ports
--------------------|--------------------------|-------------------
Chromecast | `--enable-chromecast` | gnutls
Pulseaudio | `--with-pulseaudio` | pulseaudio
PulseAudio | `--with-pulseaudio` | pulseaudio
Clone the OwnTone repo:
Clone the OwnTone repository:
```bash
git clone https://github.com/owntone/owntone-server.git
cd owntone-server
```
Finally, configure, build and install, adding configure arguments for
Finally, configure, build, install, and add configuration arguments for
optional features:
```bash
@ -177,11 +176,11 @@ make
sudo make install
```
Note: if for some reason you've installed the avahi port, you need to
Note: if for some reason you've installed the `avahi` port, you need to
add `--without-avahi` to configure above.
Edit `/usr/local/etc/owntone.conf` and change the `uid` to a nice
system daemon (eg: unknown), and run the following:
Edit `/usr/local/etc/owntone.conf` and change the `uid` to a proper
system daemon (eg: unknown), and run the following commands:
```bash
sudo mkdir -p /usr/local/var/run
@ -195,14 +194,14 @@ Run OwnTone:
sudo /usr/local/sbin/owntone
```
Verify it's running (you need to <kbd>Ctrl</kbd>+<kbd>C</kbd> to stop
Verify it is running (you need to <kbd>Ctrl</kbd>+<kbd>C</kbd> to stop
dns-sd):
```bash
dns-sd -B _daap._tcp
```
## Long version - requirements
## Long Version - Requirements
Required tools:
@ -216,53 +215,34 @@ Required tools:
Libraries:
- Avahi client libraries (avahi-client), 0.6.24 minimum
from <http://avahi.org/>
- sqlite3 3.5.0+ with unlock notify API enabled (read below)
from <http://sqlite.org/download.html>
- ffmpeg (libav)
from <http://ffmpeg.org/>
- libconfuse
from <http://www.nongnu.org/confuse/>
- libevent 2.1.4+
from <http://libevent.org/>
- MiniXML (aka mxml or libmxml)
from <http://minixml.org/software.php>
- gcrypt 1.2.0+
from <http://gnupg.org/download/index.en.html#libgcrypt>
- zlib
from <http://zlib.net/>
- libunistring 0.9.3+
from <http://www.gnu.org/software/libunistring/#downloading>
- libjson-c
from <https://github.com/json-c/json-c/wiki>
- libcurl
from <http://curl.haxx.se/libcurl/>
- libplist 0.16+
from <http://github.com/JonathanBeck/libplist/downloads>
- libsodium
from <https://download.libsodium.org/doc/>
- libprotobuf-c
from <https://github.com/protobuf-c/protobuf-c/wiki>
- libasound (optional - ALSA local audio)
- [Avahi](https://avahi.org/) client libraries (avahi-client) 0.6.24+
- [SQLite](https://sqlite.org/) 3.5.0+ with the unlock notify API enabled.
SQLite needs to be built with the support for the unlock notify API; this is not
always the case in binary packages, so you may need to rebuild SQLite to
enable the unlock notify API. You can check for the presence of the
`sqlite3_unlock_notify` symbol in the sqlite library. Refer to the `SQLITE_ENABLE_UNLOCK_NOTIFY` in the SQLlite documentation.
- [FFmpeg](https://ffmpeg.org/)
- [libconfuse](https://github.com/libconfuse/libconfuse)
- [libevent](https://libevent.org/) 2.1.4+
- [libxml2](https://gitlab.gnome.org/GNOME/libxml2)
- [Libgcrypt](https://gnupg.org/software/libgcrypt/) 1.2.0+
- [zlib](https://zlib.net/)
- [libunistring](https://www.gnu.org/software/libunistring/) 0.9.3+
- [json-c](https://github.com/json-c/json-c/)
- [libcurl](https://curl.se/libcurl/)
- [libplist](https://github.com/JonathanBeck/libplist/) 0.16+
- [libsodium](https://doc.libsodium.org/)
- [protobuf-c](https://github.com/protobuf-c/protobuf-c/)
- [alsa-lib](https://github.com/alsa-project/alsa-lib/) (optional - ALSA local audio)
often already installed as part of your distro
- libpulse (optional - Pulseaudio local audio)
from <https://www.freedesktop.org/wiki/Software/PulseAudio/Download/>
- libgnutls (optional - Chromecast support)
from <http://www.gnutls.org/>
- libwebsockets 2.0.2+ (optional - websocket support)
from <https://libwebsockets.org/>
- [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/) (optional - PulseAudio local audio)
- [GnuTLS](https://www.gnutls.org/) (optional - Chromecast support)
- [Libwebsockets](https://libwebsockets.org/) 2.0.2+ (optional - websocket support)
If using binary packages, remember that you need the development packages to
build OwnTone (usually named -dev or -devel).
Note: If using binary packages, remember that you need the development packages to
build OwnTone (usually suffixed with -dev or -devel).
sqlite3 needs to be built with support for the unlock notify API; this isn't
always the case in binary packages, so you may need to rebuild sqlite3 to
enable the unlock notify API (you can check for the presence of the
sqlite3_unlock_notify symbol in the sqlite3 library). Refer to the sqlite3
documentation, look for `SQLITE_ENABLE_UNLOCK_NOTIFY`.
## Long version - building and installing
## Long Version - Building and Installing
Start by generating the build system by running `autoreconf -i`. This will
generate the configure script and `Makefile.in`.
@ -292,13 +272,18 @@ The source for the player web interface is located under the `web-src` folder an
requires nodejs >= 6.0 to be built. In the `web-src` folder run `npm install` to
install all dependencies for the player web interface. After that run `npm run build`.
This will build the web interface and update the `htdocs` folder.
(See [Web interface](clients/web-interface.md) for more
informations)
Building with libwebsockets is required if you want the web interface. It will be enabled
if the library is present (with headers). Use `--without-libwebsockets` to disable.
To serve the web interface locally you can run `npm run serve`, which will make
it reachable at [localhost:3000](http://localhost:3000). The command expects the
server be running at [localhost:3689](http://localhost:3689) and proxies API
calls to this location. If the server is running at a different location you
can use `export VITE_OWNTONE_URL=http://owntone.local:3689`.
Building with Pulseaudio is optional. It will be enabled if the library is
Building with libwebsockets is required if you want the web interface.
It will be enabled if the library is present (with headers).
Use `--without-libwebsockets` to disable.
Building with PulseAudio is optional. It will be enabled if the library is
present (with headers). Use `--without-pulseaudio` to disable.
Recommended build settings:
@ -328,7 +313,24 @@ if it's started as root.
This user must have read permission to your library and read/write permissions
to the database location (`$localstatedir/cache/owntone` by default).
## Non-priviliged user version (for development)
## Web source formatting/linting
The source code follows certain formatting conventions for maintainability and
readability. To ensure that the source code follows these conventions,
[Prettier](https://prettier.io/) is used.
The command `npm run format` applies formatting conventions to the source code
based on a preset configuration. Note that a additional configuration is made in
the file `.prettierrc.json`.
To flag programming errors, bugs, stylistic errors and suspicious constructs in
the source code, [ESLint](https://eslint.org) is used.
ESLint has been configured following this [guide](https://vueschool.io/articles/vuejs-tutorials/eslint-and-prettier-with-vite-and-vue-js-3/).
`npm run lint` lints the source code and fixes all automatically fixable errors.
## Non-Priviliged User Version for Development
OwnTone is meant to be run as system wide daemon, but for development purposes
you may want to run it isolated to your regular user.
@ -337,6 +339,7 @@ The following description assumes that you want all runtime data stored in
`$HOME/owntone_data` and the source in `$HOME/projects/owntone-server`.
Prepare directories for runtime data:
```bash
mkdir -p $HOME/owntone_data/etc
mkdir -p $HOME/owntone_data/media
@ -345,6 +348,7 @@ mkdir -p $HOME/owntone_data/media
Copy one or more mp3 file to test with to `owntone_data/media`.
Checkout OwnTone and configure build:
```bash
cd $HOME/projects
git clone https://github.com/owntone/owntone-server.git
@ -362,10 +366,10 @@ make install
Edit `owntone_data/etc/owntone.conf`, find the following configuration settings
and set them to these values:
```
uid = ${USER}
loglevel = "debug"
directories = { "${HOME}/owntone_data/media" }
```conf
uid = ${USER}
loglevel = "debug"
directories = { "${HOME}/owntone_data/media" }
```
Run the server:
@ -373,4 +377,5 @@ Run the server:
```bash
./src/owntone -f
```
(you can also use the copy of the binary in `$HOME/owntone_data/usr/sbin`)
Note: You can also use the copy of the binary located in `$HOME/owntone_data/usr/sbin`

1
docs/changelog.md Normal file
View File

@ -0,0 +1 @@
--8<-- "ChangeLog"

View File

@ -1,24 +0,0 @@
# MPD clients
You can - to some extent - use clients for MPD to control OwnTone.
By default OwnTone listens on port 6600 for MPD clients. You can change
this in the configuration file.
Currently only a subset of the commands offered by MPD (see [MPD protocol documentation](http://www.musicpd.org/doc/protocol/))
are supported.
Due to some differences between OwnTone and MPD not all commands will act the
same way they would running MPD:
- crossfade, mixrampdb, mixrampdelay and replaygain will have no effect
- single, repeat: unlike MPD, OwnTone does not support setting single and repeat separately
on/off, instead repeat off, repeat all and repeat single are supported. Thus setting single on
will result in repeat single, repeat on results in repeat all.
The following table shows what is working for a selection of MPD clients:
| Client | Type | Status |
| --------------------------------------------- | ------ | --------------- |
| [mpc](http://www.musicpd.org/clients/mpc/) | CLI | Working commands: mpc, add, crop, current, del (ranges are not yet supported), play, next, prev (behaves like cdprev), pause, toggle, cdprev, seek, clear, outputs, enable, disable, playlist, ls, load, volume, repeat, random, single, search, find, list, update (initiates an init-rescan, the path argument is not supported) |
| [ympd](http://www.ympd.org/) | Web | Everything except "add stream" should work |

View File

@ -1,66 +0,0 @@
# Using Remote
Remote gets a list of output devices from the server; this list includes any
and all devices on the network we know of that advertise AirPlay: AirPort
Express, Apple TV, ... It also includes the local audio output, that is, the
sound card on the server (even if there is no soundcard).
OwnTone remembers your selection and the individual volume for each
output device; selected devices will be automatically re-selected, except if
they return online during playback.
## Pairing
1. Open the [web interface](http://owntone.local:3689)
2. Start Remote, go to Settings, Add Library
3. Enter the pair code in the web interface (update the page with F5 if it does
not automatically pick up the pairing request)
If Remote doesn't connect to OwnTone after you entered the pairing code
something went wrong. Check the log file to see the error message. Here are
some common reasons:
- You did not enter the correct pairing code
You will see an error in the log about pairing failure with a HTTP response code
that is *not* 0.
Solution: Try again.
- No response from Remote, possibly a network issue
If you see an error in the log with either:
- a HTTP response code that is 0
- "Empty pairing request callback"
it means that OwnTone could not establish a connection to Remote. This
might be a network issue, your router may not be allowing multicast between the
Remote device and the host OwnTone is running on.
Solution 1: Sometimes it resolves the issue if you force Remote to quit, restart
it and do the pairing proces again. Another trick is to establish some other
connection (eg SSH) from the iPod/iPhone/iPad to the host.
Solution 2: Check your router settings if you can whitelist multicast addresses
under IGMP settings. For Apple Bonjour, setting a multicast address of
224.0.0.251 and a netmask of 255.255.255.255 should work.
- Otherwise try using avahi-browse for troubleshooting:
- in a terminal, run `avahi-browse -r -k _touch-remote._tcp`
- start Remote, goto Settings, Add Library
- after a couple seconds at most, you should get something similar to this:
```
+ ath0 IPv4 59eff13ea2f98dbbef6c162f9df71b784a3ef9a3 _touch-remote._tcp local
= ath0 IPv4 59eff13ea2f98dbbef6c162f9df71b784a3ef9a3 _touch-remote._tcp local
hostname = [Foobar.local]
address = [192.168.1.1]
port = [49160]
txt = ["DvTy=iPod touch" "RemN=Remote" "txtvers=1" "RemV=10000" "Pair=FAEA410630AEC05E" "DvNm=Foobar"]
```
Hit Ctrl-C to terminate avahi-browse.
- To check for network issues you can try to connect to address and port with telnet.

View File

@ -1,43 +0,0 @@
# Supported clients
OwnTone supports these kinds of clients:
- DAAP clients, like iTunes or Rhythmbox
- Remote clients, like Apple Remote or compatibles for Android/Windows Phone
- AirPlay devices, like AirPort Express, Shairport and various AirPlay speakers
- Chromecast devices
- MPD clients, like mpc
- MP3 network stream clients, like VLC and almost any other music player
- RSP clients, like Roku Soundbridge
Like iTunes, you can control OwnTone with Remote and stream your music to
AirPlay devices.
A single OwnTone instance can handle several clients concurrently, regardless of
the protocol.
By default all clients on 192.168.* (and the ipv6 equivalent) are allowed to
connect without authentication. You can change that in the configuration file.
Here is a list of working and non-working DAAP and Remote clients. The list is
probably obsolete when you read it :-)
| Client | Developer | Type | Platform | Working (vers.) |
| ------------------------ | ----------- | ------ | ------------- | --------------- |
| iTunes | Apple | DAAP | Win | Yes (12.10.1) |
| Apple Music | Apple | DAAP | MacOS | Yes |
| Rhythmbox | Gnome | DAAP | Linux | Yes |
| Diapente | diapente | DAAP | Android | Yes |
| WinAmp DAAPClient | WardFamily | DAAP | WinAmp | Yes |
| Amarok w/DAAP plugin | KDE | DAAP | Linux/Win | Yes (2.8.0) |
| Banshee | | DAAP | Linux/Win/OSX | No (2.6.2) |
| jtunes4 | | DAAP | Java | No |
| Firefly Client | | (DAAP) | Java | No |
| Remote | Apple | Remote | iOS | Yes (4.3) |
| Retune | SquallyDoc | Remote | Android | Yes (3.5.23) |
| TunesRemote+ | Melloware | Remote | Android | Yes (2.5.3) |
| Remote for iTunes | Hyperfine | Remote | Android | Yes |
| Remote for Windows Phone | Komodex | Remote | Windows Phone | Yes (2.2.1.0) |
| TunesRemote SE | | Remote | Java | Yes (r108) |
| rtRemote for Windows | bizmodeller | Remote | Windows | Yes (1.2.0.67) |

View File

@ -1,77 +0,0 @@
# OwnTone web interface
Mobile friendly player web interface for [OwnTone](http://owntone.github.io/owntone-server/) build
with [Vue.js](https://vuejs.org), [Bulma](http://bulma.io).
You can find the web interface at [http://owntone.local:3689](http://owntone.local:3689)
or alternatively at http://SERVER_ADDRESS:3689.
Use the web interface to control playback, trigger manual library rescans, pair
with remotes, select speakers, authenticate with Spotify, etc.
## Screenshots
![Now playing](../assets/images/screenshot-now-playing.png){: class="zoom" }
![Queue](../assets/images/screenshot-queue.png){: class="zoom" }
![Music browse](../assets/images/screenshot-music-browse.png){: class="zoom" }
![Music artists](../assets/images/screenshot-music-artists.png){: class="zoom" }
![Music artist](../assets/images/screenshot-music-artist.png){: class="zoom" }
![Music albums](../assets/images/screenshot-music-albums.png){: class="zoom" }
![Music albums options](../assets/images/screenshot-music-albums-options.png){: class="zoom" }
![Music album](../assets/images/screenshot-music-album.png){: class="zoom" }
![Spotiy](../assets/images/screenshot-music-spotify.png){: class="zoom" }
![Audiobooks authors](../assets/images/screenshot-audiobooks-authors.png){: class="zoom" }
![Audiobooks](../assets/images/screenshot-audiobooks-books.png){: class="zoom" }
![Podcasts](../assets/images/screenshot-podcasts.png){: class="zoom" }
![Podcast](../assets/images/screenshot-podcast.png){: class="zoom" }
![Files](../assets/images/screenshot-files.png){: class="zoom" }
![Search](../assets/images/screenshot-search.png){: class="zoom" }
![Menu](../assets/images/screenshot-menu.png){: class="zoom" }
![Outputs](../assets/images/screenshot-outputs.png){: class="zoom" }
## Usage
You can find OwnTone's web interface at [http://owntone.local:3689](http://owntone.local:3689)
or alternatively at http://SERVER_ADDRESS:3689.
## Build Setup
The source is located in the `web-src` folder.
```
cd web-src
```
The web interface is built with [Vite](https://vitejs.dev/), makes use of Prettier for code formatting
and ESLint for code linting (the project was set up following the guide [ESLint and Prettier with Vite and Vue.js 3](https://vueschool.io/articles/vuejs-tutorials/eslint-and-prettier-with-vite-and-vue-js-3/)
``` bash
# install dependencies
npm install
# Serve with hot reload at localhost:3000
# (assumes that OwnTone server is running on localhost:3689)
npm run serve
# Serve with hot reload at localhost:3000
# (with remote OwnTone server reachable under owntone.local:3689)
VITE_OWNTONE_URL=http://owntone.local:3689 npm run serve
# Build for production with minification (will update web interface
# in "../htdocs")
npm run build
# Format code
npm run format
# Lint code (and fix errors that can be automatically fixed)
npm run lint
```
After running `npm run serve` the web interface is reachable at [localhost:3000](http://localhost:3000).
By default it expects **owntone** to be running at [localhost:3689](http://localhost:3689) and proxies all
JSON API calls to this location.
If the server is running at a different location you have to set the env variable `VITE_OWNTONE_URL`.

49
docs/configuration.md Normal file
View File

@ -0,0 +1,49 @@
# Configuration
The configuration of OwnTone is usually located in `/etc/owntone.conf`.
## Format
Each setting consists of a name and a value. There are different types of settings: string, integer, boolean, and list.
Comments are preceded by a hash sign.
The format is as follow:
```conf
# Section
section {
# String value
setting = "<string-value>"
# Integer value
setting = <integer-value>
# Boolean
setting = <true|false>
# List
setting = { "value a", "value b", "value n"}
}
```
Some settings are device specific, in which case you add a section where you specify the device name in the heading. Say you're tired of loud death metal coming from your teenager's room:
```conf
airplay "Jared's Room" {
max_volume = 3
}
```
## Most important settings
### general: uid
Identifier of the user running OwnTone.
Make sure that this user has read access to your configuration of `directories` in the `library` config section, and has write access to the database (`db_path`), cache directory (`cache_dir`) and log file (`logfile`). If you plan on using local audio then the user must also have access to that.
### library: directories
Path to the directory or directories containing the media to index (your library).
## Other settings
See the [template configuration file](https://raw.githubusercontent.com/owntone/owntone-server/refs/heads/master/owntone.conf.in) for a description of all the settings.

View File

@ -1,10 +1,30 @@
# Command line
# API and Command Line
You can choose between:
- a [MPD command line client](mpd.md) (easiest) like `mpc`
- curl with OwnTone's JSON API (see [JSON API docs](../json-api.md))
- curl with DAAP/DACP commands (hardest)
- [The JSON API](#json-api)
- [A MPD command line client like mpc](#mpc)
- [DAAP/DACP commands](#daapdacp)
The JSON API is the most versatile and the recommended method, but for simple
command line operations, mpc is easier. DAAP/DACP is only for masochists.
## JSON API
See the [JSON API docs](../json-api.md)
## mpc
[mpc](https://www.musicpd.org/clients/mpc/) is easy to use for simple operations
like enabling speakers, changing volume and getting status.
Due to differences in implementation between OwnTone and MPD, some mpc commands
will work differently or not at all.
## DAAP/DACP
Here is an example of how to use curl with DAAP/DACP. Say you have a playlist
with a radio station, and you want to make a script that starts playback of that
@ -18,7 +38,7 @@ station:
observe that you must use a session-id < 100, and that you must login and
logout):
```
```shell
curl "http://localhost:3689/login?pairing-guid=0x1&request-session-id=50"
curl "http://localhost:3689/ctrl-int/1/playspec?database-spec='dmap.persistentid:0x1'&container-spec='dmap.persistentid:0x[PLAYLIST-ID]'&container-item-spec='dmap.containeritemid:0x[FILE ID]'&session-id=50"
curl "http://localhost:3689/logout?session-id=50"

View File

@ -0,0 +1,52 @@
# Desktop Remote Control
To control OwnTone from Linux, Windows or Mac, you can use:
- [The web interface](#the-web-interface)
- [A remote for iTunes/Apple Music](#remotes-for-itunesapple-music)
- [A MPD client](#mpd-clients)
The web interface is the most feature complete and works on all platforms, so
on desktop there isn't much reason to use anything else.
However, instead of a remote control application, you can also connect to
OwnTone via a Media Client e.g. iTunes or Apple Music. Media clients will get
the media from OwnTone and do the playback themselves (remotes just control
OwnTone playback). See [Media Clients](../media-clients.md) for more
information.
## The web interface
See [web interface](web.md).
## Remotes for iTunes/Apple Music
There are only a few of these, see the below table.
| Client | Developer | Type | Platform | Working (vers.) |
| ------------------------ | ----------- | ------ | --------------- | --------------- |
| TunesRemote SE | | Remote | Java | Yes (r108) |
| rtRemote for Windows | bizmodeller | Remote | Windows | Yes (1.2.0.67) |
## MPD clients
There's a range of MPD clients available that also work with OwnTone e.g.
Cantata and Plattenalbum.
The better ones support local playback, speaker control, artwork and automatic
discovery of OwnTone's MPD server.
By default OwnTone listens on port 6600 for MPD clients. You can change
this in the configuration file.
Due to some differences between OwnTone and MPD not all commands will act the
same way they would running MPD:
- crossfade, mixrampdb, mixrampdelay and replaygain will have no effect
- single, repeat: unlike MPD, OwnTone does not support setting single and repeat
separately on/off, instead repeat off, repeat all and repeat single are
supported. Thus setting single on will result in repeat single, repeat on
results in repeat all.

View File

@ -0,0 +1,140 @@
# Mobile Remote Control
To control OwnTone from your mobile device, you can use:
- [The web interface](#the-web-interface)
- [(iOS) The Remote app from Apple](#apple-remote-app-ios)
- [(Android) An iTunes/Apple Music remote app](#remotes-for-itunesapple-music-android)
- [A MPD client app](#mpd-client-apps)
The web interface is the most feature complete, but apps may have UX advantages.
The table below shows how some features compare.
| Feature | Remote | MPD client | Web |
| ------------------------------------- | ---------- | ---------- | ---------- |
| Browse library | yes | yes | yes |
| Control playback and queue | yes | yes | yes |
| Artwork | yes | yes | yes |
| Individual speaker selection | yes | some | yes |
| Individual speaker volume | yes | no | yes |
| Volume control using phone buttons | no | ? | no |
| Listen on phone | no | some | yes |
| Access non-library Spotify tracks | no | no | yes |
| Edit and save m3u playlists | no | some | yes |
While OwnTone supports playing tracks from Spotify, there is no support for
Spotify Connect, so you can't control from the Spotify app.
## The web interface
See [web interface](web.md).
## Apple Remote app (iOS)
Remote gets a list of output devices from the server; this list includes any
and all devices on the network we know of that advertise AirPlay: AirPort
Express, Apple TV, … It also includes the local audio output, that is, the
sound card on the server (even if there is no sound card).
OwnTone remembers your selection and the individual volume for each
output device; selected devices will be automatically re-selected, except if
they return online during playback.
### Pairing
1. Open the [web interface](web.md) at either [http://owntone.local:3689](http://owntone.local:3689)
or `http://SERVER-IP-ADDRESS:3689`
2. Start Remote, go to Settings, Add Library
3. Enter the pair code in the web interface (reload the browser page if
it does not automatically pick up the pairing request)
If Remote does not connect to OwnTone after you entered the pairing code
something went wrong. Check the log file to see the error message. Here are
some common reasons:
- You did not enter the correct pairing code
You will see an error in the log about pairing failure with a HTTP response code
that is *not* 0.
Solution: Try again.
- No response from Remote, possibly a network issue
If you see an error in the log with either:
- a HTTP response code that is 0
- "Empty pairing request callback"
it means that OwnTone could not establish a connection to Remote. This
might be a network issue, your router may not be allowing multicast between the
Remote device and the host OwnTone is running on.
Solution 1: Sometimes it resolves the issue if you force Remote to quit, restart
it and do the pairing process again. Another trick is to establish some other
connection (eg SSH) from the iPod/iPhone/iPad to the host.
Solution 2: Check your router settings if you can whitelist multicast addresses
under IGMP settings. For Apple Bonjour, setting a multicast address of
224.0.0.251 and a netmask of 255.255.255.255 should work.
- Otherwise try using `avahi-browse` for troubleshooting:
- in a terminal, run:
```shell
avahi-browse -r -k _touch-remote._tcp
```
- start Remote, goto Settings, Add Library
- after a couple seconds at most, you should get something similar to this:
```shell
+ ath0 IPv4 59eff13ea2f98dbbef6c162f9df71b784a3ef9a3 _touch-remote._tcp local
= ath0 IPv4 59eff13ea2f98dbbef6c162f9df71b784a3ef9a3 _touch-remote._tcp local
hostname = [Foobar.local]
address = [192.168.1.1]
port = [49160]
txt = ["DvTy=iPod touch" "RemN=Remote" "txtvers=1" "RemV=10000" "Pair=FAEA410630AEC05E" "DvNm=Foobar"]
```
Hit Ctrl+C to terminate `avahi-browse`.
- To check for network issues you can try to connect to the server address and
port with [`nc`](https://en.wikipedia.org/wiki/Netcat) or
[`telnet`](https://en.wikipedia.org/wiki/Telnet) commands.
- Some users report that Remote can get in a state where pairing isn't possible
and nothing appears in OwnTone's logs. In that case try reinstalling Remote.
## Remotes for iTunes/Apple Music (Android)
Google Play doesn't seem to have iTunes/Apple Music remotes any more, so you
either need to use the web interface or find an apk for one of the old remotes,
like Retune (by SquallyDoc), TunesRemote+ (by Melloware) or Remote for iTunes
(by Hyperfine).
For usage and troubleshooting details, see the instructions for [Apple Remote](#apple-remote-app-ios).
## MPD client apps
There's a range of MPD clients available from app store that also work with
OwnTone e.g. MPD Pilot, MaximumMPD, Rigelian and Stylophone.
The better ones support local playback, speaker control, artwork and automatic
discovery of OwnTone's MPD server.
By default OwnTone listens on port 6600 for MPD clients. You can change
this in the configuration file.
Due to some differences between OwnTone and MPD not all commands will act the
same way they would running MPD:
- crossfade, mixrampdb, mixrampdelay and replaygain will have no effect
- single, repeat: unlike MPD, OwnTone does not support setting single and repeat
separately on/off, instead repeat off, repeat all and repeat single are
supported. Thus setting single on will result in repeat single, repeat on
results in repeat all.

View File

@ -0,0 +1,44 @@
# Web Interface
The built-in web interface is a mobile-friendly music player and browser for
OwnTone.
You can reach it at [http://owntone.local:3689](http://owntone.local:3689)
or depending on the OwnTone installation at `http://<server-address>:<port>`.
This interface becomes useful when you need to control playback, trigger
manual library rescans, pair with remotes, select speakers, grant access to
Spotify, and for many other operations.
Alternatively, you can use a MPD web client like for instance [ympd](http://www.ympd.org/).
## Screenshots
Below you have a selection of screenshots that shows different part of the
interface.
![Now playing](../assets/images/screenshot-now-playing.png){: class="zoom" }
![Queue](../assets/images/screenshot-queue.png){: class="zoom" }
![Music browse](../assets/images/screenshot-music-browse.png){: class="zoom" }
![Music artists](../assets/images/screenshot-music-artists.png){: class="zoom" }
![Music artist](../assets/images/screenshot-music-artist.png){: class="zoom" }
![Music albums](../assets/images/screenshot-music-albums.png){: class="zoom" }
![Music albums options](../assets/images/screenshot-music-albums-options.png){: class="zoom" }
![Music album](../assets/images/screenshot-music-album.png){: class="zoom" }
![Spotify](../assets/images/screenshot-music-spotify.png){: class="zoom" }
![Audiobooks authors](../assets/images/screenshot-audiobooks-authors.png){: class="zoom" }
![Audiobooks](../assets/images/screenshot-audiobooks-books.png){: class="zoom" }
![Podcasts](../assets/images/screenshot-podcasts.png){: class="zoom" }
![Podcast](../assets/images/screenshot-podcast.png){: class="zoom" }
![Files](../assets/images/screenshot-files.png){: class="zoom" }
![Search](../assets/images/screenshot-search.png){: class="zoom" }
![Menu](../assets/images/screenshot-menu.png){: class="zoom" }
![Outputs](../assets/images/screenshot-outputs.png){: class="zoom" }
## Usage
The web interface is usually reachable at [http://owntone.local:3689](http://owntone.local:3689).
But depending on the setup of OwnTone you might need to adjust the server name
and port of the server accordingly `http://<server-name>:<port>`.

88
docs/development.md Normal file
View File

@ -0,0 +1,88 @@
# Development
## Dev Containers and VSCode
To set up a development environment for OwnTone, the project includes an example Dev Containers configuration.
!!! tip "Dev Containers"
To learn more about Dev Containers and how to use them check out the documentation at:
- <https://code.visualstudio.com/docs/devcontainers/containers>
- <https://containers.dev/>
Dev Containers config for OwnTone includes all the necessary and some nice to have tooling:
- C-tools to build and develop for owntone-server, including autotools, dependencies, etc.
- Javascript-tools to build and develop the OwnTone web interface.
- Python-tools to build and run the OwnTone documentation with mkdocs.
### Prerquisites
1. Install [Docker](https://www.docker.com/get-started).
2. Install [Visual Studio Code](https://code.visualstudio.com/).
3. Install the [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension for Visual Studio Code.
### Initial setup
The Dev Container and VSCode example configuration files are located in the project folder `.dev/devcontainer` and `.dev/vscode`.
To make use of them follow these steps:
1. Copy the directories or run `make vscode` from inside the `.dev` folder.
2. Open your project in Visual Studio Code.
3. Open the Command Palette (`Ctrl+Shift+P`) and select `Dev Containers: Reopen in Container`.
4. VSCode will build the container and reopen the project inside the container.
Be patient, the first run will take several minutes to complete.
### Usage
Inside the container you can follow the build instructions (see [Building](building.md)):
- Build owntone-server
```bash
autoreconf -i
./configure
make
```
- Build web interface
```bash
cd web-src
npm run build
```
Running `owntone-server` from inside the container with the predefined run/debug configuration will
use the conf file `.devcontainer/data/devcontainer-owntone.conf` as `owntone.conf`.
Configure the mount configuration in `.devcontainer/devcontainer.json` to use a different music folder
or mount the log folder to a local directory.
```json
// Mounts volumes to keep files / state between container rebuilds
"mounts": [
//...
// Bind mounts for owntone config file and logs, cache, music directories
//"source=<path-to-local-logs-dir>,target=/data/logs,type=bind,consistency=cached",
//"source=<path-to-local-cache-dir>,target=/data/cache,type=bind,consistency=cached",
//"source=<path-to-local-music-dir>,target=/data/music,type=bind,consistency=cached",
"source=${localWorkspaceFolder}/.devcontainer/data/devcontainer-owntone.conf,target=/data/conf/owntone.conf,type=bind,consistency=cached"
],
```
### Dev Container configuration
The Dev Container example uses an Ubuntu image as base. It contains some additional (opinionated) tools to customize the shell prompt and some terminal niceties:
- [Starship](https://starship.rs/) to customize the shell prompt.
- [eza](https://eza.rocks/) as `ls` replacement.
- [Atuin](https://atuin.sh/) for the shell history.
Take a look at `.devcontainer/devcontainer.env` if you want to disable any of those.
Additional terminal tools installed are:
- [zoxide](https://github.com/ajeetdsouza/zoxide) - a smarter `cd`
- [bat](https://github.com/sharkdp/bat) - a `cat` clone with syntax highlighting

View File

@ -7,10 +7,10 @@ hide:
# OwnTone
**OwnTone** is an open source (audio) media server for GNU/Linux, FreeBSD
and MacOS.
and macOS.
It allows sharing and streaming your media library to iTunes (DAAP[^1]),
Roku (RSP), AirPlay devices (multiroom), Chromecast and also supports local
Roku (RSP), AirPlay devices (multi-room), Chromecast and also supports local
playback.
You can control OwnTone via its web interface, Apple Remote (and compatible
@ -31,7 +31,7 @@ OwnTone is written in C with a web interface written in Vue.js.
## Features
- Stream to :material-cast-variant: AirPlay (synchronized multiroom) and :material-cast:
Chromecast devices
Chromecast devices
- :material-music-box-multiple-outline: Share local library with iTunes and Roku
- :material-volume-high: Local audio playback with ALSA or PulseAudio
- Supports multiple different clients:
@ -41,11 +41,11 @@ OwnTone is written in C with a web interface written in Vue.js.
- :material-console: MPD clients
- Supports :material-music: music and :material-book-open-variant:
audiobook files, :material-microphone: podcast files and :material-rss: RSS
and :material-radio: internet radio
audiobook files, :material-microphone: podcast files and :material-rss: RSS
and :material-radio: internet radio
- :material-file-music: Supports audio files in most formats
- :material-spotify: Supports playing your Spotify library (requires
Spotify premium account)
Spotify premium account)
- :material-raspberry-pi: Runs on low power devices like the Raspberry Pi
---
@ -54,7 +54,7 @@ OwnTone is written in C with a web interface written in Vue.js.
![Music browse](assets/images/screenshot-music-browse.png){: class="zoom" }
![Music album](assets/images/screenshot-music-album.png){: class="zoom" }
_(You can find more screenshots from OwnTone's web interface [here](clients/web-interface.md))_
_(You can find more screenshots from OwnTone's web interface [here](control-clients/web.md))_
{: class="text-center" }
---
@ -66,7 +66,7 @@ and what features it was built with (e.g. Spotify support).
How to find out? Go to the [web interface](http://owntone.local:3689) and
check. No web interface? Then check the top of OwnTone's log file (usually
/var/log/owntone.log).
`/var/log/owntone.log`).
Note that you are viewing a snapshot of the instructions that may or may not
match the version of OwnTone that you are using.
@ -78,4 +78,4 @@ please see the documentation on [Building from Source](installation.md).
You can find source and documentation, also for older versions, here:
- [https://github.com/owntone/owntone-server.git](https://github.com/owntone/owntone-server.git)
- [Source Code](https://github.com/owntone/owntone-server.git)

View File

@ -6,12 +6,13 @@ instructions are [here](building.md).
Apt repositories, images and precompiled binaries are available for some
platforms. These can save you some work and make it easier to stay up to date:
Platform | How to get
----------------------|---------------------------------------------------------
RPi w/Raspberry Pi OS | Add OwnTone repository to apt sources, see:<br>[OwnTone server (iTunes server) - Raspberry Pi Forums](http://www.raspberrypi.org/phpBB3/viewtopic.php?t=49928)
Debian/Ubuntu amd64 | Download .deb as [artifact from Github workflow](https://github.com/owntone/owntone-apt/actions)<br>(requires that you are logged in)
OpenWrt | Run `opkg install libwebsockets-full owntone`
Docker | See [linuxserver/docker-daapd](https://github.com/linuxserver/docker-daapd)
|Platform | How to get
|----------------------|---------------------------------------------------------
|RPi w/Raspberry Pi OS | Add OwnTone repository to apt sources<br>(See: [Raspberry Pi Forums](http://www.raspberrypi.org/phpBB3/viewtopic.php?t=49928))
|Debian/Ubuntu amd64 | Download the .deb package as artifact from the [Github workflow](https://github.com/owntone/owntone-apt/actions)<br>(requires that you are logged in)
|OpenWrt | Run `opkg install libwebsockets-full owntone`
|Docker / Podman | See [official image](https://github.com/owntone/owntone-container)
|FreeBSD | Run `pkg install owntone` (See: [FreeBSD ports](https://cgit.freebsd.org/ports/tree/audio/owntone))
OwnTone is not in the official Debian repositories due to lack of Debian
maintainer and Debian policy difficulties concerning the web UI, see

View File

@ -5,4 +5,3 @@ go to the web interface and authorize OwnTone with your LastFM credentials.
OwnTone will not store your LastFM username/password, only the session key.
The session key does not expire.

View File

@ -44,7 +44,7 @@ The easiest way of accomplishing this may be with [Spocon](https://github.com/sp
since it requires minimal configuration. After installing, create two pipes
(with mkfifo) and set the configuration in the player section:
```
```conf
# Audio output device (MIXER, PIPE, STDOUT)
output = "PIPE"
# Output raw (signed) PCM to this file (`player.output` must be PIPE)

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,6 @@ directories only.
Files starting with . (dot) and _ (underscore) are ignored.
## Pipes (for e.g. multiroom with Shairport-sync)
Some programs, like for instance Shairport-sync, can be configured to output
@ -38,7 +37,7 @@ speakers you have selected (through Remote).
The format of the audio being written to the pipe must be PCM16.
You can also start playback of pipes manually. You will find them in remotes
You can also start playback of pipes manually. You will find them in remotes
listed under "Unknown artist" and "Unknown album". The track title will be the
name of the pipe.
@ -47,7 +46,6 @@ This requires that the metadata pipe has the same filename as the audio pipe
plus a ".metadata" suffix. Say Shairport-sync is configured to write audio to
"/foo/bar/pipe", then the metadata pipe should be "/foo/bar/pipe.metadata".
## Libraries on network mounts
Most network filesharing protocols do not offer notifications when the library
@ -57,13 +55,13 @@ Instead you can schedule a cron job to update the database.
The first step in doing this is to add two entries to the 'directories'
configuration item in owntone.conf:
```
```conf
directories = { "/some/local/dir", "/your/network/mount/library" }
```
Now you can make a cron job that runs this command:
```
```shell
touch /some/local/dir/trigger.init-rescan
```

28
docs/media-clients.md Normal file
View File

@ -0,0 +1,28 @@
# Media Clients
Media Clients are applications that download the media from the server and do
the playback themselves. OwnTone supports media clients via the DAAP and RSP
protocols (so not UPNP).
Some Media Clients are also able to play video from OwnTone.
OwnTone can't serve Spotify, internet radio and streams to Media Clients. For
that you must let OwnTone do the playback.
Here is a list of working and non-working DAAP clients. The list is probably
obsolete when you read it :-)
| Client | Developer | Type | Platform | Working (vers.) |
| ------------------------ | ----------- | ------ | --------------- | --------------- |
| iTunes | Apple | DAAP | Win | Yes (12.10.1) |
| Apple Music | Apple | DAAP | macOS | Yes |
| Rhythmbox | Gnome | DAAP | Linux | Yes |
| Diapente | diapente | DAAP | Android | Yes |
| WinAmp DAAPClient | WardFamily | DAAP | WinAmp | Yes |
| Amarok w/DAAP plugin | KDE | DAAP | Linux/Win | Yes (2.8.0) |
| Banshee | | DAAP | Linux/Win/macOS | No (2.6.2) |
| jtunes4 | | DAAP | Java | No |
| Firefly Client | | (DAAP) | Java | No |
Technically, devices like the Roku Soundbridge are both media clients and
audio outputs. You can find information about them [here](audio-outputs/roku.md).

View File

@ -1,25 +0,0 @@
# Local audio
## Local audio through ALSA
In the config file, you can select ALSA for local audio. This is the default.
When using ALSA, the server will try to syncronize playback with AirPlay. You
can adjust the syncronization in the config file.
For most setups the default values in the config file should work. If they
don't, there is help [here](../advanced/outputs-alsa.md)
## Local audio, Bluetooth and more through Pulseaudio
In the config file, you can select Pulseaudio for local audio. In addition to
local audio, Pulseaudio also supports an array of other targets, e.g. Bluetooth
or DLNA. However, Pulseaudio does require some setup, so here is a separate page
with some help on that: [Pulse audio](../advanced/outputs-pulse.md)
Note that if you select Pulseaudio the "card" setting in the config file has
no effect. Instead all soundcards detected by Pulseaudio will be listed as
speakers by OwnTone.
You can adjust the latency of Pulseaudio playback in the config file.

View File

@ -1,21 +0,0 @@
# MP3 network streaming (streaming to iOS)
You can listen to audio being played by OwnTone by opening this network
stream address in pretty much any music player:
[http://owntone.local:3689/stream.mp3](http://owntone.local:3689/stream.mp3)
or
http://SERVER_ADDRESS:3689/stream.mp3
This is currently the only way of listening to your audio on iOS devices, since
Apple does not allow AirPlay receiver apps, and because Apple Home Sharing
cannot be supported by OwnTone. So what you can do instead is install a
music player app like VLC, connect to the stream and control playback with
Remote.
In the speaker selection list, clicking on the icon should start the stream
playing in the background on browsers that support that.
Note that MP3 encoding must be supported by ffmpeg/libav for this to work. If
it is not available you will see a message in the log file. In Debian/Ubuntu you
get MP3 encoding support by installing the package "libavcodec-extra".

View File

@ -1,4 +1,4 @@
# Playlists and internet radio
# Playlists and Radio
OwnTone supports M3U and PLS playlists. Just drop your playlist somewhere
in your library with an .m3u or .pls extension and it will pick it up.

View File

@ -1,11 +1,9 @@
# OwnTone smart playlists
# Smart Playlists
To add a smart playlist to the server, create a new text file with a filename ending with .smartpl;
To add a smart playlist to the server, create a new text file with a filename ending with .smartpl;
the filename doesn't matter, only the .smartpl ending does. The file must be placed somewhere in your
library folder.
## Syntax
The contents of a smart playlist must follow the syntax:
@ -16,7 +14,6 @@ The contents of a smart playlist must follow the syntax:
There is exactly one smart playlist allowed for a .smartpl file.
An expression consists of:
```
@ -66,6 +63,9 @@ Valid operands include:
* "string value" (string)
* integer (int)
* `empty`
The `empty` operand is only valid with the `is` operator and matches items with no value for the given field-name e.g. `comment`
Valid operands for the enumeration `data_kind` are:
@ -82,9 +82,9 @@ Valid operands for the enumeration `media_kind` are:
* `audiobook`
* `tvshow`
Multiple expressions can be anded or ored together, using the keywords `OR` and `AND`. The unary not operator is also supported using the keyword `NOT`.
Use parentheses to group e.g. `play_count = 0 and (media_kind is podcast or media_kind is audiobook)`.
It is possible to define the sort order and limit the number of items by adding an order clause and/or a limit clause after the last expression:
@ -96,7 +96,6 @@ It is possible to define the sort order and limit the number of items by adding
There is additionally a special `random` _field-name_ that can be used in conjunction with `limit` to select a random number of items based on current expression.
## Examples
```
@ -144,6 +143,7 @@ This would match any podcast and audiobook file that was never played.
limit 10
}
```
This would match the last 10 music files added to the library.
```
@ -155,9 +155,21 @@ This would match the last 10 music files added to the library.
limit 10
}
```
This generates a random set of, maximum of 10, rated Pop music tracks every time the playlist is queried.
## Date operand syntax
```
"All Jazz, No Foo" {
media_kind is music and
genre is "jazz" and
(not comment includes "foo" or
comment is empty)
}
```
This matches both the songs with comments that do not include "foo", but also the songs with no comment.
## Date Operand Syntax
One example of a valid date is a date in yyyy-mm-dd format:
@ -178,7 +190,6 @@ As an example, a valid date might be:
```3 weeks before today``` or ```3 weeks ago```
Examples:
```
@ -202,13 +213,11 @@ All dates, except for `YYYY-DD-HH`, are relative to the day of when the server e
Note that `time_added after 4 weeks ago` and `time_added after last month` are subtly different; the former is exactly 4 weeks ago (from today) whereas the latter is the first day of the previous month.
## Differences with MT-daapd Smart Playlists
## Differences to mt-daapd smart playlists
The syntax is really close to the mt-daapd smart playlist syntax (see [Multi-Threaded DAAP Daemon Code](https://sourceforge.net/p/mt-daapd/code/HEAD/tree/tags/release-0.2.4.2/contrib/mt-daapd.playlist).
The syntax is really close to the mt-daapd smart playlist syntax (see
http://sourceforge.net/p/mt-daapd/code/HEAD/tree/tags/release-0.2.4.2/contrib/mt-daapd.playlist).
Even this documentation is based on the file linked above.
Even this documentation is based on the document linked above.
Some differences are:
@ -216,4 +225,3 @@ Some differences are:
* the not operator must be placed before an expression and not before the operator
* `||`, `&&`, `!` are not supported (use `or`, `and`, `not`)
* comments are not supported

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@ -11,16 +11,15 @@
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png" />
<link rel="manifest" href="site.webmanifest" />
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#5bbad5" />
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#00d1b2" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OwnTone</title>
<script type="module" crossorigin src="./assets/index.js"></script>
<link rel="stylesheet" href="./assets/index.css">
<link rel="stylesheet" crossorigin href="./assets/index.css">
</head>
<body>
<body class="has-navbar-fixed-top has-navbar-fixed-bottom">
<div id="app"></div>
</body>
</html>

View File

@ -45,7 +45,13 @@
#serial 13
AC_DEFUN([AX_PROG_FLEX], [
AC_REQUIRE([AM_PROG_LEX])
dnl --- Start of modified macro ---
dnl Original uses 'AC_REQUIRE([AM_PROG_LEX])', but that produces a deprecation
dnl warning since autoconf 2.70, because the underlying AC_PROG_LEX now
dnl requires an argument. However, we cannot specify it through AM_PROG_LEX
dnl until automake 1.17, so users with that will still get the warning.
AM_PROG_LEX([noyywrap])
dnl --- End of modified macro ---
AC_REQUIRE([AC_PROG_EGREP])
AC_CACHE_CHECK([if flex is the lexer generator],[ax_cv_prog_flex],[

View File

@ -46,18 +46,27 @@ theme:
# - navigation.indexes
- navigation.top
palette:
- scheme: default
# Palette toggle for automatic mode
- media: "(prefers-color-scheme)"
toggle:
icon: material/brightness-auto
name: Switch to light mode
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: white
accent: teal
toggle:
icon: material/toggle-switch
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
primary: blue grey
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: black
accent: teal
toggle:
icon: material/toggle-switch-off-outline
name: Switch to light mode
icon: material/brightness-4
name: Switch to system preference
font:
text: Roboto
code: Roboto Mono
@ -100,8 +109,8 @@ markdown_extensions:
- pymdownx.caret
- pymdownx.details
- pymdownx.emoji:
emoji_generator: !!python/name:materialx.emoji.to_svg
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
emoji_index: !!python/name:material.extensions.emoji.twemoji
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.inlinehilite
@ -112,6 +121,9 @@ markdown_extensions:
repo: mkdocs-material
- pymdownx.mark
- pymdownx.smartsymbols
- pymdownx.snippets:
base_path: [!relative $config_dir]
check_paths: true
- pymdownx.superfences:
custom_fences:
- name: mermaid
@ -127,31 +139,37 @@ markdown_extensions:
nav:
- Home: index.md
- Documentation:
- Getting started: getting-started.md
- Getting Started: getting-started.md
- Installation: installation.md
- Configuration: configuration.md
- Building: building.md
- Library: library.md
- Control:
- Mobile Device: control-clients/mobile.md
- Desktop: control-clients/desktop.md
- Browser: control-clients/web.md
- API and CLI: control-clients/cli-api.md
- Audio Outputs:
- AirPlay: audio-outputs/airplay.md
- Chromecast: audio-outputs/chromecast.md
- Local Audio: audio-outputs/local-audio.md
- Mobile Device: audio-outputs/mobile.md
- Web: audio-outputs/web.md
- Roku: audio-outputs/roku.md
- Streaming: audio-outputs/streaming.md
- Media Clients: media-clients.md
- Artwork: artwork.md
- Playlists & radio: playlists.md
- Smart playlists: smart-playlists.md
- Clients:
- Supported clients: clients/supported-clients.md
- Remote: clients/remote.md
- Web interface: clients/web-interface.md
- MPD clients: clients/mpd.md
- CLI: clients/cli.md
- Outputs:
- Local audio: outputs/local-audio.md
- Airplay: outputs/airplay.md
- Chromecast: outputs/chromecast.md
- Streaming: outputs/streaming.md
- Services integration:
- Playlists and Radio: playlists.md
- Smart Playlists: smart-playlists.md
- Services Integration:
- Spotify: integrations/spotify.md
- LastFM: integrations/lastfm.md
- Advanced setups:
- Alsa: advanced/outputs-alsa.md
- Pulse audio: advanced/outputs-pulse.md
- Radio streams: advanced/radio-streams.md
- Remote access: advanced/remote-access.md
- Multiple instances: advanced/multiple-instances.md
- Advanced Setup:
- ALSA: advanced/outputs-alsa.md
- PulseAudio: advanced/outputs-pulse.md
- Radio Streams: advanced/radio-streams.md
- Remote Access: advanced/remote-access.md
- Multiple Instances: advanced/multiple-instances.md
- Development: development.md
- Changelog: changelog.md
- JSON API: json-api.md

View File

@ -20,7 +20,7 @@ Debug domains; available domains are: \fIconfig\fP, \fIdaap\fP,
\fIdb\fP, \fIhttpd\fP, \fImain\fP, \fImdns\fP, \fImisc\fP,
\fIrsp\fP, \fIscan\fP, \fIxcode\fP, \fIevent\fP, \fIhttp\fP, \fIremote\fP,
\fIdacp\fP, \fIffmpeg\fP, \fIartwork\fP, \fIplayer\fP, \fIraop\fP,
\fIlaudio\fP, \fIdmap\fP, \fIfdbperf\fP, \fIspotify\fP, \fIlastfm\fP,
\fIlaudio\fP, \fIdmap\fP, \fIfdbperf\fP, \fIspotify\fP, \fIscrobble\fP,
\fIcache\fP, \fImpd\fP, \fIstream\fP, \fIcast\fP, \fIfifo\fP, \fIlib\fP,
\fIweb\fP, \fIairplay\fP.
.TP

View File

@ -40,20 +40,20 @@ general {
# Sets who is allowed to connect without authorisation. This applies to
# client types like Remotes, DAAP clients (iTunes) and to the web
# interface. Options are "any", "localhost" or the prefix to one or
# more ipv4/6 networks. The default is { "localhost", "192.168", "fd" }
# trusted_networks = { "localhost", "192.168", "fd" }
# interface. Options are "any", "lan", "localhost", "none" or the prefix
# to one or more ipv4/6 networks. The default is { "lan" }
# trusted_networks = { "lan" }
# Enable/disable IPv6
ipv6 = yes
# ipv6 = no
# Set this if you want the server to bind to a specific IP address. Can
# be ipv6 or ipv4. Default (commented out or "::") is to listen on all
# IP addresses.
# bind_address = "::"
# Location of cache database
# cache_path = "@localstatedir@/cache/@PACKAGE@/cache.db"
# Directory where the server keeps cached data
# cache_dir = "@localstatedir@/cache/@PACKAGE@"
# DAAP requests that take longer than this threshold (in msec) get their
# replies cached for next time. Set to 0 to disable caching.
@ -124,9 +124,10 @@ library {
# hide_singles = false
# Internet streams in your playlists will by default be shown in the
# "Radio" library, like iTunes does. However, some clients (like
# "Radio" library, like iTunes does. However, some DAAP clients (like
# TunesRemote+) won't show the "Radio" library. If you would also like
# to have them shown like normal playlists, you can enable this option.
# Note this option is only for DAAP clients (so not the web interface).
# radio_playlists = false
# These are the default playlists. If you want them to have other names,
@ -187,19 +188,25 @@ library {
# Should we import the content of iTunes smart playlists?
# itunes_smartpl = false
# Decoding options for DAAP and RSP clients
# Transcoding options for DAAP and RSP clients
# Since iTunes has native support for mpeg, mp4a, mp4v, alac and wav,
# such files will be sent as they are. Any other formats will be decoded
# to raw wav. If OwnTone detects a non-iTunes DAAP client, it is
# assumed to only support mpeg and wav, other formats will be decoded.
# Here you can change when to decode. Note that these settings only
# affect serving media to DAAP and RSP clients, they have no effect on
# such files will be sent as they are. Any other formats will be
# transcoded. Some other clients, including Roku/RSP, announce what
# formats they support, and the server will transcode to one of those if
# necessary. Clients that don't announce supported formats are assumed
# to support mpeg (mp3), wav and alac.
# Here you can change when and how to transcode. The settings *only*
# affect serving audio to DAAP and RSP clients, they have no effect on
# direct AirPlay, Chromecast and local audio playback.
# Formats: mp4a, mp4v, mpeg, alac, flac, mpc, ogg, wma, wmal, wmav, aif, wav
# Formats that should never be decoded
# Formats that should never be transcoded
# no_decode = { "format", "format" }
# Formats that should always be decoded
# Formats that should always be transcoded
# force_decode = { "format", "format" }
# Prefer transcode to wav (default), alac or mpeg (mp3 with the bit rate
# configured below in the streaming section). Note that alac requires
# precomputing and caching mp4 headers, which takes both cpu and disk.
# prefer_format = "format"
# Set ffmpeg filters (similar to 'ffmpeg -af xxx') that you want the
# server to use when decoding files from your library. Examples:
@ -223,6 +230,16 @@ library {
# new rating = 0.75 * stable rating + 0.25 * rolling rating)
# rating_updates = false
# By default, ratings are only saved in the server's database. Enable
# the below to make the server also read ratings from file metadata and
# write on update (requires write access). To avoid excessive writing to
# the library, automatic rating updates are not written, even with the
# write_rating option enabled.
# read_rating = false
# write_rating = false
# The scale used when reading/writing ratings to files
# max_rating = 100
# Allows creating, deleting and modifying m3u playlists in the library directories.
# Only supported by the player web interface and some mpd clients
# Defaults to being disabled.
@ -315,6 +332,10 @@ audio {
# OwnTone behind a firewall)
# control_port = 0
# timing_port = 0
# Switch Airplay 1 streams to uncompressed ALAC (as opposed to regular,
# compressed ALAC). Reduces CPU use at the cost of network bandwidth.
# uncompressed_alac = false
#}
# AirPlay per device settings
@ -417,6 +438,10 @@ mpd {
# clients and will need additional configuration in the MPD client to
# work). Set to 0 to disable serving artwork over http.
# http_port = 0
# Whether to emit an output with plugin type "httpd" to tell clients
# that a stream is available for local playback.
# enable_httpd_plugin = false
}
# SQLite configuration (allows to modify the operation of the SQLite databases)

View File

@ -19,7 +19,7 @@ Url: https://github.com/owntone/owntone-server
Source0: https://github.com/owntone/%{name}/archive/%{version}/%{name}-%{version}.tar.xz
%{?systemd_ordering}
BuildRequires: gcc, make, bison, flex, systemd, pkgconfig, libunistring-devel
BuildRequires: pkgconfig(zlib), pkgconfig(libconfuse), pkgconfig(mxml)
BuildRequires: pkgconfig(zlib), pkgconfig(libconfuse), pkgconfig(libxml-2.0)
BuildRequires: pkgconfig(sqlite3) >= 3.5.0, pkgconfig(libevent) >= 2.0.0
BuildRequires: pkgconfig(json-c), libgcrypt-devel >= 1.2.0
BuildRequires: libgpg-error-devel >= 1.6

View File

@ -20,7 +20,7 @@ if [ "$yn" != "y" ]; then
fi
DEPS="gmake autoconf automake libtool gettext gperf glib pkgconf wget git \
ffmpeg libconfuse libevent mxml libgcrypt libunistring libiconv curl \
ffmpeg libconfuse libevent libxml2 libgcrypt libunistring libiconv curl \
libplist libinotify avahi sqlite3 alsa-lib libsodium json-c libwebsockets
protobuf-c bison flex"
echo "The script can install the following dependency packages for you:"

View File

@ -53,8 +53,8 @@ GPERF_FILES = \
GPERF_SRC = $(GPERF_FILES:.gperf=_hash.h)
LEXER_SRC = parsers/daap_lexer.l parsers/smartpl_lexer.l parsers/rsp_lexer.l
PARSER_SRC = parsers/daap_parser.y parsers/smartpl_parser.y parsers/rsp_parser.y
LEXER_SRC = parsers/daap_lexer.l parsers/smartpl_lexer.l parsers/rsp_lexer.l parsers/mpd_lexer.l
PARSER_SRC = parsers/daap_parser.y parsers/smartpl_parser.y parsers/rsp_parser.y parsers/mpd_parser.y
# This flag is given to Bison and tells it to produce headers. Note that
# automake recognizes this flag too, and has special logic around it, so don't
@ -88,6 +88,7 @@ owntone_SOURCES = main.c \
library/filescanner.c library/filescanner.h \
library/filescanner_ffmpeg.c library/filescanner_playlist.c \
library/filescanner_smartpl.c library/filescanner_itunes.c \
library/filescanner_mountwatch.c \
library/rssscanner.c \
library.c library.h \
$(MDNS_SRC) mdns.h \
@ -107,6 +108,7 @@ owntone_SOURCES = main.c \
artwork.c artwork.h \
misc.c misc.h \
misc_json.c misc_json.h \
misc_xml.c misc_xml.h \
rng.c rng.h \
smartpl_query.c smartpl_query.h \
player.c player.h \
@ -125,10 +127,10 @@ owntone_SOURCES = main.c \
evthr.c evthr.h \
$(SPOTIFY_SRC) \
$(LASTFM_SRC) \
listenbrainz.c listenbrainz.h \
$(MPD_SRC) \
listener.c listener.h \
commands.c commands.h \
mxml-compat.h \
outputs/plist_wrap.h \
$(LIBWEBSOCKETS_SRC) \
$(GPERF_SRC) \

View File

@ -71,7 +71,7 @@
// See online_source_is_failing()
#define ONLINE_SEARCH_COOLDOWN_TIME 3600
#define ONLINE_SEARCH_FAILURES_MAX 3
#define ONLINE_SEARCH_FAILURES_MAX 5
enum artwork_cache
{
@ -113,6 +113,8 @@ struct artwork_ctx {
uint32_t media_kind;
// Input data for group handlers
int64_t persistentid;
// Input data for queue item handlers
struct db_queue_item *queue_item;
// Not to be used by handler - query for item or group
struct query_params qp;
@ -192,6 +194,8 @@ static const char *cover_extension[] =
"jpg", "png",
};
static pthread_mutex_t artwork_cache_stash_mutex = PTHREAD_MUTEX_INITIALIZER;
/* ----------------- DECLARE AND CONFIGURE SOURCE HANDLERS ----------------- */
/* Forward - group handlers */
@ -208,6 +212,8 @@ static int source_item_ownpl_get(struct artwork_ctx *ctx);
static int source_item_spotifywebapi_search_get(struct artwork_ctx *ctx);
static int source_item_discogs_get(struct artwork_ctx *ctx);
static int source_item_coverartarchive_get(struct artwork_ctx *ctx);
/* Forward - queue item handlers */
static int source_queue_item_artwork_url_get(struct artwork_ctx *ctx);
/* List of sources that can provide artwork for a group (i.e. usually an album
* identified by a persistentid). The source handlers will be called in the
@ -351,6 +357,24 @@ static struct artwork_source artwork_item_source[] =
}
};
/* List of sources that can provide artwork for a queue item. The source
* handlers will be called in the order of this list. Must be terminated by a
* NULL struct.
*/
static struct artwork_source artwork_queue_item_source[] =
{
{
.name = "artwork url",
.handler = source_queue_item_artwork_url_get,
.cache = NEVER,
},
{
.name = NULL,
.handler = NULL,
.cache = 0,
}
};
/* Forward - parsers of online source responses */
static enum parse_result response_jparse_spotify(char **artwork_url, json_object *response, int max_w, int max_h);
static enum parse_result response_jparse_discogs(char **artwork_url, json_object *response, int max_w, int max_h);
@ -457,7 +481,6 @@ artwork_read_byurl(struct evbuffer *evbuf, const char *url)
ret = http_client_request(&client, NULL);
if (ret < 0)
{
DPRINTF(E_LOG, L_ART, "Request to '%s' failed with return value %d\n", url, ret);
goto out;
}
@ -604,6 +627,8 @@ size_calculate(int *dst_w, int *dst_h, int src_w, int src_h, int max_w, int max_
static int
artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is_embedded, enum data_kind data_kind, struct artwork_req_params req_params)
{
struct transcode_decode_setup_args xcode_decode_args = { .profile = XCODE_JPEG }; // Covers XCODE_PNG too
struct transcode_encode_setup_args xcode_encode_args = { 0 };
struct decode_ctx *xcode_decode = NULL;
struct encode_ctx *xcode_encode = NULL;
struct transcode_evbuf_io xcode_evbuf_io = { 0 };
@ -637,13 +662,16 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is
}
xcode_evbuf_io.evbuf = xcode_buf;
xcode_decode = transcode_decode_setup(XCODE_JPEG, NULL, data_kind, NULL, &xcode_evbuf_io, 0); // Covers XCODE_PNG too
xcode_decode_args.evbuf_io = &xcode_evbuf_io;
xcode_decode_args.is_http = (data_kind == DATA_KIND_HTTP);
}
else
{
xcode_decode = transcode_decode_setup(XCODE_JPEG, NULL, data_kind, path, NULL, 0); // Covers XCODE_PNG too
xcode_decode_args.path = path;
xcode_decode_args.is_http = (data_kind == DATA_KIND_HTTP);
}
xcode_decode = transcode_decode_setup(xcode_decode_args);
if (!xcode_decode)
{
if (path)
@ -702,15 +730,19 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is
goto out;
}
xcode_encode_args.src_ctx = xcode_decode;
xcode_encode_args.width = dst_width;
xcode_encode_args.height = dst_height;
if (dst_format == ART_FMT_JPEG)
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, NULL, dst_width, dst_height);
xcode_encode_args.profile = XCODE_JPEG;
else if (dst_format == ART_FMT_PNG)
xcode_encode = transcode_encode_setup(XCODE_PNG, NULL, xcode_decode, NULL, dst_width, dst_height);
xcode_encode_args.profile = XCODE_PNG;
else if (dst_format == ART_FMT_VP8)
xcode_encode = transcode_encode_setup(XCODE_VP8, NULL, xcode_decode, NULL, dst_width, dst_height);
xcode_encode_args.profile = XCODE_VP8;
else
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, NULL, dst_width, dst_height);
xcode_encode_args.profile = XCODE_JPEG;
xcode_encode = transcode_encode_setup(xcode_encode_args);
if (!xcode_encode)
{
if (path)
@ -918,7 +950,7 @@ artwork_get_bydir(struct evbuffer *evbuf, char *out_path, size_t len, char *dir,
* before making a request. Stashes result in cache, also if negative.
*
* @out artwork Image data
* @in url URL of the artwork
* @in url HTTP(S) URL of the artwork
* @in req_params Requested max size/format
* @return ART_FMT_* on success, ART_E_NONE or ART_E_ERROR
*/
@ -932,12 +964,18 @@ artwork_get_byurl(struct evbuffer *artwork, const char *url, struct artwork_req_
CHECK_NULL(L_ART, raw = evbuffer_new());
format = ART_E_ERROR;
// Accessing the cache is thread safe, the purpose of the lock is to make the
// artwork stash more effective if we have parallel requests for the same url.
// It will assure that the artwork from the first request is downloaded and
// stashed before processing the next request.
pthread_mutex_lock(&artwork_cache_stash_mutex);
ret = cache_artwork_read(raw, url, &format);
if (ret < 0)
{
format = artwork_read_byurl(raw, url);
cache_artwork_stash(raw, url, format);
}
pthread_mutex_unlock(&artwork_cache_stash_mutex);
// If we couldn't read, or we have cached a negative result from the last attempt, we stop now
if (format <= 0)
@ -1114,7 +1152,7 @@ online_source_response_parse(char **artwork_url, const struct online_source *src
}
static int
online_source_request_url_make(char *url, size_t url_size, const struct online_source *src, struct artwork_ctx *ctx)
online_source_search_url_make(char *url, size_t url_size, const struct online_source *src, struct artwork_ctx *ctx)
{
struct db_queue_item *queue_item;
struct keyval query = { 0 };
@ -1157,6 +1195,13 @@ online_source_request_url_make(char *url, size_t url_size, const struct online_s
goto error;
}
if ((artist && strncmp(CFG_NAME_UNKNOWN_ARTIST, artist, strlen(CFG_NAME_UNKNOWN_ARTIST)) == 0) ||
(album && strncmp(CFG_NAME_UNKNOWN_ALBUM, album, strlen(CFG_NAME_UNKNOWN_ARTIST)) == 0) )
{
DPRINTF(E_DBG, L_ART, "Skipping online artwork search for unknown artist/album\n");
goto error;
}
for (i = 0; src->query_parts[i].key; i++)
{
if (!album && strstr(src->query_parts[i].template, "$ALBUM$"))
@ -1176,14 +1221,17 @@ online_source_request_url_make(char *url, size_t url_size, const struct online_s
ret = keyval_add(&query, src->query_parts[i].key, param);
if (ret < 0)
{
DPRINTF(E_LOG, L_ART, "keyval_add() failed in request_url_make()\n");
DPRINTF(E_LOG, L_ART, "keyval_add() failed in online_source_request_url_make()\n");
goto error;
}
}
encoded_query = http_form_urlencode(&query);
if (!encoded_query)
goto error;
{
DPRINTF(E_WARN, L_ART, "URL encoding for online artwork search failed\n");
goto error;
}
snprintf(url, url_size, "%s?%s", src->search_endpoint, src->search_param);
if (safe_snreplace(url, url_size, "$QUERY$", encoded_query) < 0)
@ -1211,18 +1259,13 @@ online_source_search_check_last(char **last_artwork_url, const struct online_sou
struct online_search_history *history = src->search_history;
bool is_same;
pthread_mutex_lock(&history->mutex);
is_same = (hash == history->last_hash) &&
(max_w == history->last_max_w) &&
(max_h == history->last_max_h);
// Copy this to the caller while we have the lock anyway
if (is_same)
*last_artwork_url = safe_strdup(history->last_artwork_url);
pthread_mutex_unlock(&history->mutex);
return is_same ? 0 : -1;
}
@ -1232,8 +1275,6 @@ online_source_is_failing(const struct online_source *src, int id)
struct online_search_history *history = src->search_history;
bool is_failing;
pthread_mutex_lock(&history->mutex);
// If the last request was more than ONLINE_SEARCH_COOLDOWN_TIME ago we will always try again
if (time(NULL) > history->last_timestamp + ONLINE_SEARCH_COOLDOWN_TIME)
is_failing = false;
@ -1250,22 +1291,20 @@ online_source_is_failing(const struct online_source *src, int id)
else
is_failing = true;
pthread_mutex_unlock(&history->mutex);
return is_failing;
}
static void
online_source_history_update(const struct online_source *src, int id, uint32_t request_hash, int response_code, const char *artwork_url)
online_source_history_update(const struct online_source *src, int id, uint32_t request_hash, int response_code, const char *artwork_url, int max_w, int max_h)
{
struct online_search_history *history = src->search_history;
pthread_mutex_lock(&history->mutex);
history->last_id = id;
history->last_hash = request_hash;
history->last_response_code = response_code;
history->last_timestamp = time(NULL);
history->last_max_w = max_w;
history->last_max_h = max_h;
free(history->last_artwork_url);
history->last_artwork_url = safe_strdup(artwork_url); // FIXME should free this on exit
@ -1274,14 +1313,12 @@ online_source_history_update(const struct online_source *src, int id, uint32_t r
history->count_failures = 0;
else
history->count_failures++;
pthread_mutex_unlock(&history->mutex);
}
static int
auth_header_add(struct keyval *headers, const struct online_source *src)
{
char auth_header[256];
char auth_header[512];
char *auth_key;
char *auth_secret;
int ret;
@ -1311,34 +1348,24 @@ auth_header_add(struct keyval *headers, const struct online_source *src)
}
static char *
online_source_search(const struct online_source *src, struct artwork_ctx *ctx)
online_source_artwork_url_get(const char *search_url, const struct online_source *src, int id, int max_w, int max_h)
{
char *artwork_url;
struct http_client_ctx client = { 0 };
struct keyval output_headers = { 0 };
uint32_t hash;
char url[2048];
int ret;
DPRINTF(E_SPAM, L_ART, "Trying %s for %s\n", src->name, ctx->dbmfi->path);
ret = online_source_request_url_make(url, sizeof(url), src, ctx);
if (ret < 0)
{
DPRINTF(E_WARN, L_ART, "Skipping artwork source %s, could not construct a request URL\n", src->name);
return NULL;
}
// Be nice to our peer + improve response times by not repeating search requests
hash = djb_hash(url, strlen(url));
ret = online_source_search_check_last(&artwork_url, src, hash, ctx->req_params.max_w, ctx->req_params.max_h);
hash = djb_hash(search_url, strlen(search_url));
ret = online_source_search_check_last(&artwork_url, src, hash, max_w, max_h);
if (ret == 0)
{
return artwork_url; // Will be NULL if we are repeating a search that failed
}
// If our recent searches have been futile we may give the source a break
if (online_source_is_failing(src, ctx->id))
if (online_source_is_failing(src, id))
{
DPRINTF(E_DBG, L_ART, "Skipping artwork source %s, too many failed requests\n", src->name);
return NULL;
@ -1351,18 +1378,18 @@ online_source_search(const struct online_source *src, struct artwork_ctx *ctx)
}
CHECK_NULL(L_ART, client.input_body = evbuffer_new());
client.url = url;
client.url = search_url;
client.output_headers = &output_headers;
ret = http_client_request(&client, NULL);
keyval_clear(&output_headers);
if (ret < 0 || client.response_code != HTTP_OK)
{
DPRINTF(E_WARN, L_ART, "Artwork request to '%s' failed, response code %d\n", url, client.response_code);
DPRINTF(E_WARN, L_ART, "Artwork request to '%s' failed, response code %d\n", search_url, client.response_code);
goto error;
}
ret = online_source_response_parse(&artwork_url, src, client.input_body, ctx->req_params.max_w, ctx->req_params.max_h);
ret = online_source_response_parse(&artwork_url, src, client.input_body, max_w, max_h);
if (ret == ONLINE_SOURCE_PARSE_NOT_FOUND)
DPRINTF(E_DBG, L_ART, "No image tag found in response from source '%s'\n", src->name);
else if (ret == ONLINE_SOURCE_PARSE_INVALID)
@ -1375,16 +1402,41 @@ online_source_search(const struct online_source *src, struct artwork_ctx *ctx)
if (ret != ONLINE_SOURCE_PARSE_OK)
goto error;
online_source_history_update(src, ctx->id, hash, client.response_code, artwork_url);
online_source_history_update(src, id, hash, client.response_code, artwork_url, max_w, max_h);
evbuffer_free(client.input_body);
return artwork_url;
error:
online_source_history_update(src, ctx->id, hash, client.response_code, NULL);
online_source_history_update(src, id, hash, client.response_code, NULL, max_w, max_h);
evbuffer_free(client.input_body);
return NULL;
}
static char *
online_source_search(const struct online_source *src, struct artwork_ctx *ctx)
{
struct online_search_history *history = src->search_history;
char search_url[2048];
char *artwork_url;
int ret;
DPRINTF(E_SPAM, L_ART, "Trying %s for %s\n", src->name, ctx->dbmfi->path);
ret = online_source_search_url_make(search_url, sizeof(search_url), src, ctx);
if (ret < 0)
return NULL;
// The protection against flooding the online source with requests requires
// that online_source_request() isn't called in parallel
pthread_mutex_lock(&history->mutex);
artwork_url = online_source_artwork_url_get(search_url, src, ctx->id, ctx->req_params.max_w, ctx->req_params.max_h);
pthread_mutex_unlock(&history->mutex);
return artwork_url;
}
static bool
online_source_is_enabled(const struct online_source *src)
{
@ -1577,29 +1629,23 @@ source_item_own_get(struct artwork_ctx *ctx)
return artwork_get(ctx->evbuf, path, NULL, false, ctx->data_kind, ctx->req_params);
}
/*
* Downloads the artwork from the location pointed to by queue_item->artwork_url
*/
static int
source_item_artwork_url_get(struct artwork_ctx *ctx)
{
struct db_queue_item *queue_item;
int ret;
DPRINTF(E_SPAM, L_ART, "Trying artwork url for %s\n", ctx->dbmfi->path);
queue_item = db_queue_fetch_byfileid(ctx->id);
if (!queue_item || !queue_item->artwork_url)
if (ctx->queue_item)
{
free_queue_item(queue_item, 0);
return ART_E_NONE;
ret = source_queue_item_artwork_url_get(ctx);
}
else
{
ctx->queue_item = db_queue_fetch_byfileid(ctx->id);
ret = source_queue_item_artwork_url_get(ctx);
free_queue_item(ctx->queue_item, 0);
}
ret = artwork_get_byurl(ctx->evbuf, queue_item->artwork_url, ctx->req_params);
snprintf(ctx->path, sizeof(ctx->path), "%s", queue_item->artwork_url);
free_queue_item(queue_item, 0);
return ret;
}
@ -1613,17 +1659,22 @@ static int
source_item_pipe_get(struct artwork_ctx *ctx)
{
struct db_queue_item *queue_item;
const char *proto = "file:";
const char *proto_file = "file:";
bool is_file;
char *path;
int ret;
DPRINTF(E_SPAM, L_ART, "Trying pipe metadata from %s.metadata\n", ctx->dbmfi->path);
queue_item = db_queue_fetch_byfileid(ctx->id);
if (!queue_item || !queue_item->artwork_url || strncmp(queue_item->artwork_url, proto, strlen(proto)) != 0)
if (!queue_item || !queue_item->artwork_url)
goto notfound;
path = queue_item->artwork_url + strlen(proto);
is_file = (strncmp(queue_item->artwork_url, proto_file, strlen(proto_file)) == 0);
if (!is_file)
goto notfound;
path = queue_item->artwork_url + strlen(proto_file);
// Sometimes the file has been replaced, but queue_item->artwork_url hasn't
// been updated yet. In that case just stop now.
@ -1807,11 +1858,46 @@ source_item_ownpl_get(struct artwork_ctx *ctx)
return format;
}
/*
* Downloads artwork from ctx->queue_item->artwork_url
*/
static int
source_queue_item_artwork_url_get(struct artwork_ctx *ctx)
{
const char *proto_http = "http:";
const char *proto_https = "https:";
const char *artwork_url;
bool is_http;
bool is_https;
int ret;
if (!ctx->queue_item || !ctx->queue_item->artwork_url)
goto notfound;
DPRINTF(E_SPAM, L_ART, "Trying artwork url for %s\n", ctx->queue_item->path);
artwork_url = ctx->queue_item->artwork_url;
is_http = (strncmp(artwork_url, proto_http, strlen(proto_http)) == 0);
is_https = (strncmp(artwork_url, proto_https, strlen(proto_https)) == 0);
if (!is_http && !is_https)
goto notfound;
ret = artwork_get_byurl(ctx->evbuf, artwork_url, ctx->req_params);
snprintf(ctx->path, sizeof(ctx->path), "%s", artwork_url);
return ret;
notfound:
return ART_E_NONE;
}
/* --------------------------- SOURCE PROCESSING --------------------------- */
static int
process_items(struct artwork_ctx *ctx, int item_mode)
process_file_items(struct artwork_ctx *ctx, int item_mode)
{
struct db_media_file_info dbmfi;
int i;
@ -1955,16 +2041,55 @@ process_group(struct artwork_ctx *ctx)
}
invalid_group:
return process_items(ctx, 0);
return process_file_items(ctx, 0);
}
static int
process_queue_item(struct artwork_ctx *ctx)
{
const char *source_name;
int i;
int ret;
for (i = 0; artwork_queue_item_source[i].handler; i++)
{
// If just one handler says we should not cache a negative result then we obey that
if ((artwork_queue_item_source[i].cache & ON_FAILURE) == 0)
ctx->cache = NEVER;
source_name = artwork_queue_item_source[i].name;
DPRINTF(E_SPAM, L_ART, "Checking queue item source '%s'\n", source_name);
ret = artwork_queue_item_source[i].handler(ctx);
if (ret > 0)
{
DPRINTF(E_DBG, L_ART, "Artwork for queue item '%s' found in source '%s'\n", ctx->queue_item->title, source_name);
ctx->cache = artwork_queue_item_source[i].cache;
return ret;
}
else if (ret == ART_E_ABORT)
{
DPRINTF(E_DBG, L_ART, "Source '%s' stopped search for artwork for queue item '%s'\n", source_name, ctx->queue_item->title);
ctx->cache = NEVER;
}
else if (ret == ART_E_ERROR)
{
DPRINTF(E_LOG, L_ART, "Source '%s' returned an error for queue item '%s'\n", source_name, ctx->queue_item->title);
ctx->cache = NEVER;
}
}
return -1;
}
/* ------------------------------ ARTWORK API ------------------------------ */
int
artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h, int format)
artwork_get_by_file_id(struct evbuffer *evbuf, int id, int max_w, int max_h, int format)
{
struct artwork_ctx ctx;
struct artwork_ctx ctx = { 0 };
char filter[32];
int ret;
@ -1973,8 +2098,6 @@ artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h, int forma
if (id == DB_MEDIA_FILE_NON_PERSISTENT_ID)
return -1;
memset(&ctx, 0, sizeof(struct artwork_ctx));
ctx.qp.type = Q_ITEMS;
ctx.qp.filter = filter;
ctx.evbuf = evbuf;
@ -1993,7 +2116,7 @@ artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h, int forma
// Note: process_items will set ctx.persistentid for the following process_group()
// - and do nothing else if artwork_individual is not configured by user
ret = process_items(&ctx, 1);
ret = process_file_items(&ctx, 1);
if (ret > 0)
{
if (ctx.cache & ON_SUCCESS)
@ -2023,15 +2146,13 @@ artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h, int forma
}
int
artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h, int format)
artwork_get_by_group_id(struct evbuffer *evbuf, int id, int max_w, int max_h, int format)
{
struct artwork_ctx ctx;
struct artwork_ctx ctx = { 0 };
int ret;
DPRINTF(E_DBG, L_ART, "Artwork request for group %d (max_w=%d, max_h=%d)\n", id, max_w, max_h);
memset(&ctx, 0, sizeof(struct artwork_ctx));
/* Get the persistent id for the given group id */
ret = db_group_persistentid_byid(id, &ctx.persistentid);
if (ret < 0)
@ -2066,6 +2187,48 @@ artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h, int form
return -1;
}
int
artwork_get_by_queue_item_id(struct evbuffer *evbuf, int item_id, int max_w, int max_h, int format)
{
struct artwork_ctx ctx = { 0 };
struct db_queue_item *queue_item;
int ret;
DPRINTF(E_DBG, L_ART, "Artwork request for queue item %d (max_w=%d, max_h=%d)\n", item_id, max_w, max_h);
queue_item = db_queue_fetch_byitemid(item_id);
if (!queue_item)
return -1;
if (queue_item->file_id != DB_MEDIA_FILE_NON_PERSISTENT_ID)
{
ret = artwork_get_by_file_id(evbuf, queue_item->file_id, max_w, max_h, format);
free_queue_item(queue_item, 0);
return ret;
}
ctx.queue_item = queue_item;
ctx.evbuf = evbuf;
ctx.req_params.max_w = max_w;
ctx.req_params.max_h = max_h;
ctx.req_params.format = format;
ctx.cache = ON_FAILURE;
ret = process_queue_item(&ctx);
if (ret > 0)
{
// No caching of queue item artwork implemented as of yet
free_queue_item(queue_item, 0);
return ret;
}
DPRINTF(E_DBG, L_ART, "No artwork found for queue item %d\n", item_id);
free_queue_item(queue_item, 0);
return -1;
}
/* Checks if the file is an artwork file */
bool
artwork_file_is_artwork(const char *filename)

View File

@ -13,7 +13,7 @@
#include <stdbool.h>
/*
* Get the artwork image for an individual item (track)
* Get the artwork image for an individual library item (track)
*
* @out evbuf Event buffer that will contain the (scaled) image
* @in id The mfi item id
@ -23,7 +23,7 @@
* @return ART_FMT_* on success, -1 on error or no artwork found
*/
int
artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h, int format);
artwork_get_by_file_id(struct evbuffer *evbuf, int id, int max_w, int max_h, int format);
/*
* Get the artwork image for a group (an album or an artist)
@ -36,7 +36,21 @@ artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h, int forma
* @return ART_FMT_* on success, -1 on error or no artwork found
*/
int
artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h, int format);
artwork_get_by_group_id(struct evbuffer *evbuf, int id, int max_w, int max_h, int format);
/*
* Get the artwork image for a queue item. If the queue item is in the library,
* this will return the same as artwork_get_by_file_id
*
* @out evbuf Event buffer that will contain the (scaled) image
* @in item_id The queue item id
* @in max_w Requested maximum image width (may not be obeyed)
* @in max_h Requested maximum image height (may not be obeyed)
* @in format Requested format (may not be obeyed), 0 for default
* @return ART_FMT_* on success, -1 on error or no artwork found
*/
int
artwork_get_by_queue_item_id(struct evbuffer *evbuf, int item_id, int max_w, int max_h, int format);
/*
* Checks if the file is an artwork file (based on user config)

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
#include <event2/buffer.h>
/* ---------------------------- DAAP cache API --------------------------- */
/* ----------------------------- DAAP cache API ---------------------------- */
void
cache_daap_suspend(void);
@ -19,10 +19,19 @@ void
cache_daap_add(const char *query, const char *ua, int is_remote, int msec);
int
cache_daap_threshold(void);
cache_daap_threshold_get(void);
/* ---------------------------- Artwork cache API --------------------------- */
/* --------------------------- Transcode cache API ------------------------- */
int
cache_xcode_header_get(struct evbuffer *evbuf, int *cached, uint32_t id, const char *format);
int
cache_xcode_toggle(bool enable);
/* ---------------------------- Artwork cache API -------------------------- */
#define CACHE_ARTWORK_GROUP 0
#define CACHE_ARTWORK_INDIVIDUAL 1
@ -48,7 +57,7 @@ cache_artwork_stash(struct evbuffer *evbuf, const char *path, int format);
int
cache_artwork_read(struct evbuffer *evbuf, const char *path, int *format);
/* ---------------------------- Cache API --------------------------- */
/* ------------------------------- Cache API ------------------------------- */
int
cache_init(void);

View File

@ -49,13 +49,15 @@ static cfg_opt_t sec_general[] =
CFG_STR("db_backup_path", NULL, CFGF_NONE),
CFG_STR("logfile", STATEDIR "/log/" PACKAGE ".log", CFGF_NONE),
CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel),
CFG_STR("logformat", "default", CFGF_NONE),
CFG_STR("admin_password", NULL, CFGF_NONE),
CFG_INT("websocket_port", 3688, CFGF_NONE),
CFG_STR("websocket_interface", NULL, CFGF_NONE),
CFG_STR_LIST("trusted_networks", "{localhost,192.168,fd}", CFGF_NONE),
CFG_BOOL("ipv6", cfg_true, CFGF_NONE),
CFG_STR_LIST("trusted_networks", "{lan}", CFGF_NONE),
CFG_BOOL("ipv6", cfg_false, CFGF_NONE),
CFG_STR("bind_address", NULL, CFGF_NONE),
CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
CFG_STR("cache_dir", STATEDIR "/cache/" PACKAGE, CFGF_NONE),
CFG_STR("cache_path", NULL, CFGF_DEPRECATED),
CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
CFG_BOOL("speaker_autoselect", cfg_false, CFGF_NONE),
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
@ -67,6 +69,9 @@ static cfg_opt_t sec_general[] =
CFG_INT("db_pragma_cache_size", -1, CFGF_NONE),
CFG_STR("db_pragma_journal_mode", NULL, CFGF_NONE),
CFG_INT("db_pragma_synchronous", -1, CFGF_NONE),
CFG_STR("cache_daap_filename", "daap.db", CFGF_NONE),
CFG_STR("cache_artwork_filename", "artwork.db", CFGF_NONE),
CFG_STR("cache_xcode_filename", "xcode.db", CFGF_NONE),
CFG_STR("allow_origin", "*", CFGF_NONE),
CFG_STR("user_agent", PACKAGE_NAME "/" PACKAGE_VERSION, CFGF_NONE),
CFG_BOOL("ssl_verifypeer", cfg_true, CFGF_NONE),
@ -111,10 +116,14 @@ static cfg_opt_t sec_library[] =
CFG_BOOL("itunes_smartpl", cfg_false, CFGF_NONE),
CFG_STR_LIST("no_decode", NULL, CFGF_NONE),
CFG_STR_LIST("force_decode", NULL, CFGF_NONE),
CFG_STR("prefer_format", NULL, CFGF_NONE),
CFG_BOOL("pipe_autostart", cfg_true, CFGF_NONE),
CFG_INT("pipe_sample_rate", 44100, CFGF_NONE),
CFG_INT("pipe_bits_per_sample", 16, CFGF_NONE),
CFG_BOOL("rating_updates", cfg_false, CFGF_NONE),
CFG_BOOL("read_rating", cfg_false, CFGF_NONE),
CFG_BOOL("write_rating", cfg_false, CFGF_NONE),
CFG_INT("max_rating", 100, CFGF_NONE),
CFG_BOOL("allow_modifying_stored_playlists", cfg_false, CFGF_NONE),
CFG_STR("default_playlist_directory", NULL, CFGF_NONE),
CFG_BOOL("clear_queue_on_stop_disable", cfg_false, CFGF_NONE),
@ -156,6 +165,7 @@ static cfg_opt_t sec_airplay_shared[] =
{
CFG_INT("control_port", 0, CFGF_NONE),
CFG_INT("timing_port", 0, CFGF_NONE),
CFG_BOOL("uncompressed_alac", cfg_false, CFGF_NONE),
CFG_END()
};
@ -208,6 +218,12 @@ static cfg_opt_t sec_spotify[] =
CFG_BOOL("base_playlist_disable", cfg_false, CFGF_NONE),
CFG_BOOL("artist_override", cfg_false, CFGF_NONE),
CFG_BOOL("album_override", cfg_false, CFGF_NONE),
CFG_BOOL("disable_legacy_mode", cfg_false, CFGF_NONE),
// Issued by Spotify on developer.spotify.com for forked-daapd/OwnTone
CFG_STR("webapi_client_id", "0e684a5422384114a8ae7ac020f01789", CFGF_NONE),
CFG_STR("webapi_client_secret", "232af95f39014c9ba218285a5c11a239", CFGF_NONE),
// Must be in allow-list on developer.spotify.com for forked-daapd/OwnTone
CFG_STR("redirect_uri", "https://owntone.github.io/owntone-oauth/spotify/", CFGF_NONE),
CFG_END()
};
@ -229,6 +245,7 @@ static cfg_opt_t sec_mpd[] =
{
CFG_INT("port", 6600, CFGF_NONE),
CFG_INT("http_port", 0, CFGF_NONE),
CFG_BOOL("enable_httpd_plugin", cfg_false, CFGF_NONE),
CFG_BOOL("clear_queue_on_stop_disable", cfg_false, CFGF_NODEFAULT | CFGF_DEPRECATED),
CFG_BOOL("allow_modifying_stored_playlists", cfg_false, CFGF_NODEFAULT | CFGF_DEPRECATED),
CFG_STR("default_playlist_directory", NULL, CFGF_NODEFAULT | CFGF_DEPRECATED),
@ -307,6 +324,31 @@ cb_loglevel(cfg_t *config, cfg_opt_t *opt, const char *value, void *result)
return 0;
}
// Makes sure cache_dir ends with a slash
static int
sanitize_cache_dir(cfg_t *general)
{
char *dir;
const char *s;
char *appended;
size_t len;
dir = cfg_getstr(general, "cache_dir");
len = strlen(dir);
s = strrchr(dir, '/');
if (s && (s + 1 == dir + len))
return 0;
appended = safe_asprintf("%s/", dir);
cfg_setstr(general, "cache_dir", appended);
free(appended);
return 0;
}
static int
conffile_expand_libname(cfg_t *lib)
{
@ -420,7 +462,6 @@ conffile_expand_libname(cfg_t *lib)
return 0;
}
int
conffile_load(char *file)
{
@ -461,6 +502,14 @@ conffile_load(char *file)
runas_uid = pw->pw_uid;
runas_gid = pw->pw_gid;
ret = sanitize_cache_dir(cfg_getsec(cfg, "general"));
if (ret != 0)
{
DPRINTF(E_FATAL, L_CONF, "Invalid configuration of cache_dir\n");
goto out_fail;
}
lib = cfg_getsec(cfg, "library");
if (cfg_size(lib, "directories") == 0)

310
src/db.c
View File

@ -102,6 +102,7 @@ enum fixup_type {
};
struct db_unlock {
char thread_name_tid[32];
int proceed;
pthread_cond_t cond;
pthread_mutex_t lck;
@ -230,6 +231,7 @@ static const struct col_type_map mfi_cols_map[] =
{ "channels", mfi_offsetof(channels), DB_TYPE_INT },
{ "usermark", mfi_offsetof(usermark), DB_TYPE_INT },
{ "scan_kind", mfi_offsetof(scan_kind), DB_TYPE_INT },
{ "lyrics", mfi_offsetof(lyrics), DB_TYPE_STRING },
};
/* This list must be kept in sync with
@ -371,6 +373,7 @@ static const ssize_t dbmfi_cols_map[] =
dbmfi_offsetof(channels),
dbmfi_offsetof(usermark),
dbmfi_offsetof(scan_kind),
dbmfi_offsetof(lyrics),
};
/* This list must be kept in sync with
@ -650,6 +653,7 @@ db_pl_type_label(enum pl_type pl_type)
struct rng_ctx shuffle_rng;
static char *db_path;
static char *db_sqlite_ext_path;
static bool db_rating_updates;
static __thread sqlite3 *hdl;
@ -777,6 +781,7 @@ free_mfi(struct media_file_info *mfi, int content_only)
free(mfi->composer_sort);
free(mfi->album_artist_sort);
free(mfi->virtual_path);
free(mfi->lyrics);
if (!content_only)
free(mfi);
@ -841,6 +846,7 @@ free_query_params(struct query_params *qp, int content_only)
free(qp->filter);
free(qp->having);
free(qp->order);
free(qp->group);
if (!content_only)
free(qp);
@ -1135,7 +1141,7 @@ fixup_defaults(char **tag, enum fixup_type fixup, struct fixup_ctx *ctx)
case DB_FIXUP_SONGALBUMID:
if (ctx->mfi && ctx->mfi->songalbumid == 0)
ctx->mfi->songalbumid = two_str_hash(ctx->mfi->album_artist, ctx->mfi->album);
ctx->mfi->songalbumid = two_str_hash(ctx->mfi->album_artist, ctx->mfi->album) + ctx->mfi->data_kind;
break;
case DB_FIXUP_TITLE:
@ -1441,6 +1447,7 @@ unlock_notify_cb(void **args, int nargs)
{
u = (struct db_unlock *)args[i];
DPRINTF(E_DBG, L_DB, "Notify DB unlock, thread: %s\n", u->thread_name_tid);
CHECK_ERR(L_DB, pthread_mutex_lock(&u->lck));
u->proceed = 1;
@ -1456,6 +1463,8 @@ db_wait_unlock(void)
struct db_unlock u;
int ret;
thread_getnametid(u.thread_name_tid, sizeof(u.thread_name_tid));
u.proceed = 0;
CHECK_ERR(L_DB, mutex_init(&u.lck));
CHECK_ERR(L_DB, pthread_cond_init(&u.cond, NULL));
@ -1472,7 +1481,7 @@ db_wait_unlock(void)
}
CHECK_ERR(L_DB, pthread_mutex_unlock(&u.lck));
}
}
CHECK_ERR(L_DB, pthread_cond_destroy(&u.cond));
CHECK_ERR(L_DB, pthread_mutex_destroy(&u.lck));
@ -1966,6 +1975,8 @@ db_free_query_clause(struct query_clause *qc)
free(qc);
}
// Builds the generic parts of the query. Parts that are specific to the query
// type are in db_build_query_* implementations.
static struct query_clause *
db_build_query_clause(struct query_params *qp)
{
@ -2073,8 +2084,21 @@ db_build_query_items(struct query_params *qp, struct query_clause *qc)
char *count;
char *query;
count = sqlite3_mprintf("SELECT COUNT(*) FROM files f %s;", qc->where);
query = sqlite3_mprintf("SELECT f.* FROM files f %s %s %s %s;", qc->where, qc->group, qc->order, qc->index);
if (qp->id == 0)
{
count = sqlite3_mprintf("SELECT COUNT(*) FROM files f %s;", qc->where);
query = sqlite3_mprintf("SELECT f.* FROM files f %s %s %s %s;", qc->where, qc->group, qc->order, qc->index);
}
else if (qc->where[0] == '\0')
{
count = sqlite3_mprintf("SELECT COUNT(*) FROM files f WHERE f.id = %d;", qp->id);
query = sqlite3_mprintf("SELECT f.* FROM files f WHERE f.id = %d %s %s %s;", qp->id, qc->group, qc->order, qc->index);
}
else
{
count = sqlite3_mprintf("SELECT COUNT(*) FROM files f %s AND f.id = %d;", qc->where, qp->id);
query = sqlite3_mprintf("SELECT f.* FROM files f %s AND f.id = %d %s %s %s;", qc->where, qp->id, qc->group, qc->order, qc->index);
}
return db_build_query_check(qp, count, query);
}
@ -2903,7 +2927,9 @@ db_file_inc_playcount_byfilter(const char *filter)
return;
}
ret = db_query_run(query, 1, 0);
// Perhaps this should in principle emit LISTENER_DATABASE, but that would
// cause a lot of useless cache updates
ret = db_query_run(query, 1, db_rating_updates ? LISTENER_RATING : 0);
if (ret == 0)
db_admin_setint64(DB_ADMIN_DB_MODIFIED, (int64_t) time(NULL));
#undef Q_TMPL
@ -2969,7 +2995,7 @@ db_file_inc_skipcount(int id)
return;
}
ret = db_query_run(query, 1, 0);
ret = db_query_run(query, 1, db_rating_updates ? LISTENER_RATING : 0);
if (ret == 0)
db_admin_setint64(DB_ADMIN_DB_MODIFIED, (int64_t) time(NULL));
#undef Q_TMPL
@ -3137,6 +3163,30 @@ db_file_id_byquery(const char *query)
return ret;
}
bool
db_file_id_exists(int id)
{
#define Q_TMPL "SELECT f.id FROM files f WHERE f.id = %d;"
char *query;
int ret;
query = sqlite3_mprintf(Q_TMPL, id);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return 0;
}
ret = db_file_id_byquery(query);
sqlite3_free(query);
return (id == ret);
#undef Q_TMPL
}
int
db_file_id_bypath(const char *path)
{
@ -3210,13 +3260,37 @@ db_file_id_byurl(const char *url)
}
int
db_file_id_by_virtualpath_match(const char *path)
db_file_id_byvirtualpath(const char *virtual_path)
{
#define Q_TMPL "SELECT f.id FROM files f WHERE f.virtual_path = %Q;"
char *query;
int ret;
query = sqlite3_mprintf(Q_TMPL, virtual_path);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return 0;
}
ret = db_file_id_byquery(query);
sqlite3_free(query);
return ret;
#undef Q_TMPL
}
int
db_file_id_byvirtualpath_match(const char *virtual_path)
{
#define Q_TMPL "SELECT f.id FROM files f WHERE f.virtual_path LIKE '%%%q%%';"
char *query;
int ret;
query = sqlite3_mprintf(Q_TMPL, path);
query = sqlite3_mprintf(Q_TMPL, virtual_path);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
@ -3436,67 +3510,6 @@ db_file_seek_update(int id, uint32_t seek)
#undef Q_TMPL
}
static int
db_file_rating_update(char *query)
{
int ret;
ret = db_query_run(query, 1, 0);
if (ret == 0)
{
db_admin_setint64(DB_ADMIN_DB_MODIFIED, (int64_t) time(NULL));
listener_notify(LISTENER_RATING);
}
return ((ret < 0) ? -1 : sqlite3_changes(hdl));
}
int
db_file_rating_update_byid(uint32_t id, uint32_t rating)
{
#define Q_TMPL "UPDATE files SET rating = %d WHERE id = %d;"
char *query;
query = sqlite3_mprintf(Q_TMPL, rating, id);
return db_file_rating_update(query);
#undef Q_TMPL
}
int
db_file_rating_update_byvirtualpath(const char *virtual_path, uint32_t rating)
{
#define Q_TMPL "UPDATE files SET rating = %d WHERE virtual_path = %Q;"
char *query;
query = sqlite3_mprintf(Q_TMPL, rating, virtual_path);
return db_file_rating_update(query);
#undef Q_TMPL
}
int
db_file_usermark_update_byid(uint32_t id, uint32_t usermark)
{
#define Q_TMPL "UPDATE files SET usermark = %d WHERE id = %d;"
char *query;
int ret;
query = sqlite3_mprintf(Q_TMPL, usermark, id);
ret = db_query_run(query, 1, 0);
if (ret == 0)
{
db_admin_setint64(DB_ADMIN_DB_MODIFIED, (int64_t) time(NULL));
listener_notify(LISTENER_UPDATE);
}
return ((ret < 0) ? -1 : sqlite3_changes(hdl));
#undef Q_TMPL
}
void
db_file_delete_bypath(const char *path)
{
@ -4780,10 +4793,10 @@ db_admin_delete(const char *key)
int
db_speaker_save(struct output_device *device)
{
#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key) VALUES (%" PRIi64 ", %d, %d, %Q, %Q);"
#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key, format) VALUES (%" PRIi64 ", %d, %d, %Q, %Q, %d);"
char *query;
query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key);
query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key, device->selected_format);
return db_query_run(query, 1, 0);
#undef Q_TMPL
@ -4792,7 +4805,7 @@ db_speaker_save(struct output_device *device)
int
db_speaker_get(struct output_device *device, uint64_t id)
{
#define Q_TMPL "SELECT s.selected, s.volume, s.name, s.auth_key FROM speakers s WHERE s.id = %" PRIi64 ";"
#define Q_TMPL "SELECT s.selected, s.volume, s.name, s.auth_key, s.format FROM speakers s WHERE s.id = %" PRIi64 ";"
sqlite3_stmt *stmt;
char *query;
int ret;
@ -4834,6 +4847,8 @@ db_speaker_get(struct output_device *device, uint64_t id)
free(device->auth_key);
device->auth_key = safe_strdup((char *)sqlite3_column_text(stmt, 3));
device->selected_format = sqlite3_column_int(stmt, 4);
#ifdef DB_PROFILE
while (db_blocking_step(stmt) == SQLITE_ROW)
; /* EMPTY */
@ -5271,62 +5286,6 @@ db_queue_add_by_query(struct query_params *qp, char reshuffle, uint32_t item_id,
return ret;
}
/*
* Adds the items of the stored playlist with the given id to the end of the queue
*
* @param plid Id of the stored playlist
* @param reshuffle If 1 queue will be reshuffled after adding new items
* @param item_id The base item id, all items after this will be reshuffled
* @param position The position in the queue for the new queue item, -1 to add at end of queue
* @param count If not NULL returns the number of items added to the queue
* @param new_item_id If not NULL return the queue item id of the first new queue item
* @return 0 on success, -1 on failure
*/
int
db_queue_add_by_playlistid(int plid, char reshuffle, uint32_t item_id, int position, int *count, int *new_item_id)
{
struct query_params qp;
int ret;
memset(&qp, 0, sizeof(struct query_params));
qp.id = plid;
qp.type = Q_PLITEMS;
ret = db_queue_add_by_query(&qp, reshuffle, item_id, position, count, new_item_id);
return ret;
}
/*
* Adds the file with the given id to the queue
*
* @param id Id of the file
* @param reshuffle If 1 queue will be reshuffled after adding new items
* @param item_id The base item id, all items after this will be reshuffled
* @param position The position in the queue for the new queue item, -1 to add at end of queue
* @param count If not NULL returns the number of items added to the queue
* @param new_item_id If not NULL return the queue item id of the first new queue item
* @return 0 on success, -1 on failure
*/
int
db_queue_add_by_fileid(int id, char reshuffle, uint32_t item_id, int position, int *count, int *new_item_id)
{
struct query_params qp;
char buf[124];
int ret;
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
snprintf(buf, sizeof(buf), "f.id = %" PRIu32, id);
qp.filter = buf;
ret = db_queue_add_by_query(&qp, reshuffle, item_id, position, count, new_item_id);
return ret;
}
static int
queue_enum_start(struct query_params *qp)
{
@ -5338,7 +5297,9 @@ queue_enum_start(struct query_params *qp)
qp->stmt = NULL;
if (qp->sort)
if (qp->order)
orderby = qp->order;
else if (qp->sort)
orderby = sort_clause[qp->sort];
else
orderby = sort_clause[S_POS];
@ -6049,6 +6010,48 @@ db_queue_move_bypos(int pos_from, int pos_to)
return ret;
}
int
db_queue_move_bypos_range(int range_begin, int range_end, int pos_to)
{
#define Q_TMPL "UPDATE queue SET pos = CASE WHEN pos < %d THEN pos + %d ELSE pos - %d END, queue_version = %d WHERE pos >= %d AND pos < %d;"
int queue_version;
char *query;
int count;
int update_begin;
int update_end;
int ret;
int cut_off;
int offset_up;
int offset_down;
queue_version = queue_transaction_begin();
count = range_end - range_begin;
update_begin = MIN(range_begin, pos_to);
update_end = MAX(range_begin + count, pos_to + count);
if (range_begin < pos_to)
{
cut_off = range_begin + count;
offset_up = pos_to - range_begin;
offset_down = count;
}
else
{
cut_off = range_begin;
offset_up = count;
offset_down = range_begin - pos_to;
}
query = sqlite3_mprintf(Q_TMPL, cut_off, offset_up, offset_down, queue_version, update_begin, update_end);
ret = db_query_run(query, 1, 0);
queue_transaction_end(ret, queue_version);
return ret;
#undef Q_TMPL
}
/*
* Moves the queue item at the given position to the given target position. The positions
* are relavtive to the given base item (item id).
@ -6388,8 +6391,6 @@ db_watch_get_byquery(struct watch_info *wi, char *query)
ret = db_blocking_step(stmt);
if (ret != SQLITE_ROW)
{
DPRINTF(E_WARN, L_DB, "Watch not found: '%s'\n", query);
sqlite3_finalize(stmt);
sqlite3_free(query);
return -1;
@ -6615,7 +6616,7 @@ db_watch_enum_fetchwd(struct watch_enum *we, uint32_t *wd)
ret = db_blocking_step(we->stmt);
if (ret == SQLITE_DONE)
{
DPRINTF(E_INFO, L_DB, "End of watch enum results\n");
DPRINTF(E_DBG, L_DB, "End of watch enum results\n");
return 0;
}
else if (ret != SQLITE_ROW)
@ -6943,9 +6944,6 @@ db_open(void)
int synchronous;
int mmap_size;
if (!db_path)
return -1;
ret = sqlite3_open(db_path, &hdl);
if (ret != SQLITE_OK)
{
@ -6964,7 +6962,7 @@ db_open(void)
return -1;
}
ret = sqlite3_load_extension(hdl, PKGLIBDIR "/" PACKAGE_NAME "-sqlext.so", NULL, &errmsg);
ret = sqlite3_load_extension(hdl, db_sqlite_ext_path, NULL, &errmsg);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Could not load SQLite extension: %s\n", errmsg);
@ -7107,7 +7105,10 @@ db_statements_prepare_ping(const char *table)
sqlite3_stmt *stmt;
int ret;
CHECK_NULL(L_DB, query = db_mprintf("UPDATE %s SET db_timestamp = ?, disabled = 0 WHERE path = ? AND db_timestamp >= ?;", table));
// The last param will be the file mtime. We must not update if the mtime is
// newer or equal than the current db_timestamp, since the file may have been
// modified and must be rescanned.
CHECK_NULL(L_DB, query = db_mprintf("UPDATE %s SET db_timestamp = ?, disabled = 0 WHERE path = ? AND db_timestamp > ?;", table));
ret = db_blocking_prepare_v2(query, -1, &stmt, NULL);
if (ret != SQLITE_OK)
@ -7144,8 +7145,9 @@ db_statements_prepare(void)
return 0;
}
// Returns -2 if backup not enabled in config
int
db_backup()
db_backup(void)
{
int ret;
sqlite3 *backup_hdl;
@ -7165,7 +7167,7 @@ db_backup()
if (realpath(db_path, resolved_dbp) == NULL || realpath(backup_path, resolved_bp) == NULL)
{
DPRINTF(E_LOG, L_DB, "Failed to resolve real path of db/backup path: %s\n", strerror(errno));
goto error;
return -1;
}
if (strcmp(resolved_bp, resolved_dbp) == 0)
@ -7180,14 +7182,15 @@ db_backup()
if (ret != SQLITE_OK)
{
DPRINTF(E_WARN, L_DB, "Failed to create backup '%s': %s\n", backup_path, sqlite3_errmsg(backup_hdl));
goto error;
return -1;
}
backup = sqlite3_backup_init(backup_hdl, "main", hdl, "main");
if (!backup)
{
DPRINTF(E_WARN, L_DB, "Failed to initiate backup '%s': %s\n", backup_path, sqlite3_errmsg(backup_hdl));
goto error;
sqlite3_close(backup_hdl);
return -1;
}
ret = sqlite3_backup_step(backup, -1);
@ -7199,10 +7202,7 @@ db_backup()
else
DPRINTF(E_WARN, L_DB, "Failed to complete backup '%s': %s (%d)\n", backup_path, sqlite3_errstr(ret), ret);
return ret;
error:
return -1;
return 0;
}
int
@ -7376,7 +7376,7 @@ db_check_version(void)
}
int
db_init(void)
db_init(char *sqlite_ext_path)
{
uint32_t files;
uint32_t pls;
@ -7393,37 +7393,38 @@ db_init(void)
}
db_path = cfg_getstr(cfg_getsec(cfg, "general"), "db_path");
db_sqlite_ext_path = sqlite_ext_path;
db_rating_updates = cfg_getbool(cfg_getsec(cfg, "library"), "rating_updates");
DPRINTF(E_LOG, L_DB, "Configured to use database file '%s'\n", db_path);
DPRINTF(E_INFO, L_DB, "Configured to use database file '%s'\n", db_path);
ret = sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
if (ret != SQLITE_OK)
{
DPRINTF(E_FATAL, L_DB, "Could not switch SQLite3 to multithread mode\n");
DPRINTF(E_FATAL, L_DB, "Check that SQLite3 has been configured for thread-safe operations\n");
return -1;
goto error;
}
ret = sqlite3_enable_shared_cache(1);
if (ret != SQLITE_OK)
{
DPRINTF(E_FATAL, L_DB, "Could not enable SQLite3 shared-cache mode\n");
return -1;
goto error;
}
ret = sqlite3_initialize();
if (ret != SQLITE_OK)
{
DPRINTF(E_FATAL, L_DB, "SQLite3 failed to initialize\n");
return -1;
goto error;
}
ret = db_open();
if (ret < 0)
{
DPRINTF(E_FATAL, L_DB, "Could not open database\n");
return -1;
goto error;
}
ret = db_check_version();
@ -7432,7 +7433,7 @@ db_init(void)
DPRINTF(E_FATAL, L_DB, "Database version check errored out, incompatible database\n");
db_perthread_deinit();
return -1;
goto error;
}
else if (ret > 0)
{
@ -7443,7 +7444,7 @@ db_init(void)
{
DPRINTF(E_FATAL, L_DB, "Could not create tables\n");
db_perthread_deinit();
return -1;
goto error;
}
}
@ -7461,6 +7462,9 @@ db_init(void)
rng_init(&shuffle_rng);
return 0;
error:
return -1;
}
void

View File

@ -72,6 +72,7 @@ enum query_type {
#define DB_ADMIN_START_TIME "start_time"
#define DB_ADMIN_LASTFM_SESSION_KEY "lastfm_sk"
#define DB_ADMIN_SPOTIFY_REFRESH_TOKEN "spotify_refresh_token"
#define DB_ADMIN_LISTENBRAINZ_TOKEN "listenbrainz_token"
/* Max value for media_file_info->rating (valid range is from 0 to 100) */
#define DB_FILES_RATING_MAX 100
@ -248,6 +249,7 @@ struct media_file_info {
char *composer_sort;
uint32_t scan_kind; /* Identifies the library_source that created/updates this item */
char *lyrics;
};
#define mfi_offsetof(field) offsetof(struct media_file_info, field)
@ -422,6 +424,7 @@ struct db_media_file_info {
char *channels;
char *usermark;
char *scan_kind;
char *lyrics;
};
#define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field)
@ -683,6 +686,9 @@ db_file_ping_bymatch(const char *path, int isdir);
char *
db_file_path_byid(int id);
bool
db_file_id_exists(int id);
int
db_file_id_bypath(const char *path);
@ -693,7 +699,10 @@ int
db_file_id_byurl(const char *url);
int
db_file_id_by_virtualpath_match(const char *path);
db_file_id_byvirtualpath(const char *virtual_path);
int
db_file_id_byvirtualpath_match(const char *virtual_path);
struct media_file_info *
db_file_fetch_byid(int id);
@ -710,15 +719,6 @@ db_file_update(struct media_file_info *mfi);
void
db_file_seek_update(int id, uint32_t seek);
int
db_file_rating_update_byid(uint32_t id, uint32_t rating);
int
db_file_usermark_update_byid(uint32_t id, uint32_t usermark);
int
db_file_rating_update_byvirtualpath(const char *virtual_path, uint32_t rating);
void
db_file_delete_bypath(const char *path);
@ -899,12 +899,6 @@ db_queue_add_by_queryafteritemid(struct query_params *qp, uint32_t item_id);
int
db_queue_add_by_query(struct query_params *qp, char reshuffle, uint32_t item_id, int position, int *count, int *new_item_id);
int
db_queue_add_by_playlistid(int plid, char reshuffle, uint32_t item_id, int position, int *count, int *new_item_id);
int
db_queue_add_by_fileid(int id, char reshuffle, uint32_t item_id, int position, int *count, int *new_item_id);
int
db_queue_add_start(struct db_queue_add_info *queue_add_info, int pos);
@ -962,6 +956,9 @@ db_queue_move_byitemid(uint32_t item_id, int pos_to, char shuffle);
int
db_queue_move_bypos(int pos_from, int pos_to);
int
db_queue_move_bypos_range(int range_begin, int range_end, int pos_to);
int
db_queue_move_byposrelativetoitem(uint32_t from_pos, uint32_t to_offset, uint32_t item_id, char shuffle);
@ -1024,7 +1021,7 @@ int
db_watch_enum_fetchwd(struct watch_enum *we, uint32_t *wd);
int
db_backup();
db_backup(void);
int
db_perthread_init(void);
@ -1033,7 +1030,7 @@ void
db_perthread_deinit(void);
int
db_init(void);
db_init(char *sqlite_ext_path);
void
db_deinit(void);

View File

@ -98,7 +98,8 @@
" composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" channels INTEGER DEFAULT 0," \
" usermark INTEGER DEFAULT 0," \
" scan_kind INTEGER DEFAULT 0" \
" scan_kind INTEGER DEFAULT 0," \
" lyrics TEXT DEFAULT NULL COLLATE DAAP" \
");"
#define T_PL \
@ -150,8 +151,9 @@
" id INTEGER PRIMARY KEY NOT NULL," \
" selected INTEGER NOT NULL," \
" volume INTEGER NOT NULL," \
" name VARCHAR(255) DEFAULT NULL," \
" auth_key VARCHAR(2048) DEFAULT NULL" \
" name VARCHAR(255) DEFAULT NULL," \
" auth_key VARCHAR(2048) DEFAULT NULL," \
" format INTEGER DEFAULT 0" \
");"
#define T_INOTIFY \

View File

@ -26,7 +26,7 @@
* is a major upgrade. In other words minor version upgrades permit downgrading
* the server after the database was upgraded. */
#define SCHEMA_VERSION_MAJOR 22
#define SCHEMA_VERSION_MINOR 0
#define SCHEMA_VERSION_MINOR 2
int
db_init_indices(sqlite3 *hdl);

View File

@ -1227,6 +1227,44 @@ static const struct db_upgrade_query db_upgrade_v2200_queries[] =
{ U_v2200_SCVER_MINOR, "set schema_version_minor to 00" },
};
/* ---------------------------- 22.00 -> 22.01 ------------------------------ */
#define U_v2201_ALTER_FILES_ADD_LYRICS \
"ALTER TABLE files ADD COLUMN lyrics TEXT DEFAULT NULL COLLATE DAAP;"
#define U_v2201_SCVER_MAJOR \
"UPDATE admin SET value = '22' WHERE key = 'schema_version_major';"
#define U_v2201_SCVER_MINOR \
"UPDATE admin SET value = '01' WHERE key = 'schema_version_minor';"
static const struct db_upgrade_query db_upgrade_v2201_queries[] =
{
{ U_v2201_ALTER_FILES_ADD_LYRICS, "alter table files add column lyrics" },
{ U_v2201_SCVER_MAJOR, "set schema_version_major to 22" },
{ U_v2201_SCVER_MINOR, "set schema_version_minor to 01" },
};
/* ---------------------------- 22.01 -> 22.02 ------------------------------ */
#define U_v2202_ALTER_SPEAKERS_ADD_FORMAT \
"ALTER TABLE speakers ADD COLUMN format INTEGER DEFAULT 0;"
#define U_v2202_SCVER_MAJOR \
"UPDATE admin SET value = '22' WHERE key = 'schema_version_major';"
#define U_v2202_SCVER_MINOR \
"UPDATE admin SET value = '02' WHERE key = 'schema_version_minor';"
static const struct db_upgrade_query db_upgrade_v2202_queries[] =
{
{ U_v2202_ALTER_SPEAKERS_ADD_FORMAT, "alter table speakers add column format" },
{ U_v2202_SCVER_MAJOR, "set schema_version_major to 22" },
{ U_v2202_SCVER_MINOR, "set schema_version_minor to 02" },
};
/* -------------------------- Main upgrade handler -------------------------- */
int
@ -1437,6 +1475,19 @@ db_upgrade(sqlite3 *hdl, int db_ver)
if (ret < 0)
return -1;
/* FALLTHROUGH */
case 2200:
ret = db_generic_upgrade(hdl, db_upgrade_v2201_queries, ARRAY_SIZE(db_upgrade_v2201_queries));
if (ret < 0)
return -1;
/* FALLTHROUGH */
case 2201:
ret = db_generic_upgrade(hdl, db_upgrade_v2202_queries, ARRAY_SIZE(db_upgrade_v2202_queries));
if (ret < 0)
return -1;
/* Last case statement is the only one that ends with a break statement! */
break;

View File

@ -224,7 +224,6 @@ dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval
{
switch (df->type)
{
case DMAP_TYPE_DATE:
case DMAP_TYPE_UBYTE:
case DMAP_TYPE_USHORT:
case DMAP_TYPE_UINT:
@ -247,6 +246,7 @@ dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval
val.v_u64 = 0;
break;
case DMAP_TYPE_DATE:
case DMAP_TYPE_LONG:
ret = safe_atoi64(strval, &val.v_i64);
if (ret < 0)
@ -360,12 +360,11 @@ dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errms
}
int
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav)
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags)
{
const struct dmap_field_map *dfm;
const struct dmap_field *df;
char **strval;
char *ptr;
int32_t val;
int want_mikd;
int want_asdk;
@ -444,40 +443,7 @@ dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, stru
continue;
}
val = 0;
if (force_wav)
{
switch (dfm->mfi_offset)
{
case dbmfi_offsetof(type):
ptr = "wav";
strval = &ptr;
break;
case dbmfi_offsetof(bitrate):
val = 0;
ret = safe_atoi32(dbmfi->samplerate, &val);
if ((ret < 0) || (val == 0))
val = 1411;
else
val = (val * 8) / 250;
ptr = NULL;
strval = &ptr;
break;
case dbmfi_offsetof(description):
ptr = "wav audio file";
strval = &ptr;
break;
default:
break;
}
}
dmap_add_field(song, df, *strval, val);
dmap_add_field(song, df, *strval, 0);
DPRINTF(E_SPAM, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval);
}

View File

@ -79,7 +79,7 @@ void
dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errmsg);
int
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav);
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags);
int
dmap_encode_queue_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_queue_item *queue_item);

View File

@ -36,6 +36,7 @@
#include <libavutil/opt.h>
#include <event2/event.h>
#include <event2/keyvalq_struct.h>
#include <curl/curl.h>
@ -171,7 +172,7 @@ http_client_request(struct http_client_ctx *ctx, struct http_client_session *ses
res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
DPRINTF(E_LOG, L_HTTP, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res));
DPRINTF(E_WARN, L_HTTP, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res));
curl_slist_free_all(headers);
if (!session)
{
@ -193,6 +194,41 @@ http_client_request(struct http_client_ctx *ctx, struct http_client_session *ses
return 0;
}
int
http_form_urldecode(struct keyval *kv, const char *uri)
{
struct evhttp_uri *ev_uri = NULL;
struct evkeyvalq ev_query = { 0 };
struct evkeyval *param;
const char *query;
int ret;
ev_uri = evhttp_uri_parse_with_flags(uri, EVHTTP_URI_NONCONFORMANT);
if (!ev_uri)
return -1;
query = evhttp_uri_get_query(ev_uri);
if (!query)
goto error;
ret = evhttp_parse_query_str(query, &ev_query);
if (ret < 0)
goto error;
// musl libc doesn't have sys/queue.h so don't use TAILQ_FOREACH
for (param = ev_query.tqh_first; param; param = param->next.tqe_next)
keyval_add(kv, param->key, param->value);
evhttp_uri_free(ev_uri);
evhttp_clear_headers(&ev_query);
return 0;
error:
evhttp_uri_free(ev_uri);
evhttp_clear_headers(&ev_query);
return -1;
}
char *
http_form_urlencode(struct keyval *kv)
{

View File

@ -21,7 +21,7 @@ struct http_client_ctx
*/
const char *url;
struct keyval *output_headers;
char *output_body;
const char *output_body;
/* A keyval/evbuf to store response headers and body.
* Can be set to NULL to ignore that part of the response.
@ -37,10 +37,6 @@ struct http_client_ctx
/* HTTP Response code */
int response_code;
/* Private */
int ret;
void *evbase;
};
struct http_icy_metadata
@ -86,6 +82,14 @@ http_client_request(struct http_client_ctx *ctx, struct http_client_session *ses
char *
http_form_urlencode(struct keyval *kv);
/* The reverse of http_form_urlencode, except takes a full url as input.
*
* @param kv keyval struct allocated by caller where values will be added
* @param url with the query to decode
* @return 0 if ok, otherwise -1
*/
int
http_form_urldecode(struct keyval *kv, const char *uri);
/* Returns a newly allocated string with the first stream in the m3u given in
* url. If url is not a m3u, the string will be a copy of url.

View File

@ -34,10 +34,6 @@
#include <stdint.h>
#include <inttypes.h>
#ifdef HAVE_SYSCALL
#include <sys/syscall.h> // get thread ID
#endif
#include <event2/event.h>
#include <regex.h>
@ -52,9 +48,13 @@
#include "httpd.h"
#include "httpd_internal.h"
#include "transcode.h"
#include "cache.h"
#include "listener.h"
#include "player.h"
#ifdef LASTFM
# include "lastfm.h"
#endif
#include "listenbrainz.h"
#ifdef HAVE_LIBWEBSOCKETS
# include "websocket.h"
#endif
@ -66,10 +66,6 @@
"<h1>%s</h1>\n" \
"</body>\n</html>\n"
#define HTTPD_STREAM_SAMPLE_RATE 44100
#define HTTPD_STREAM_BPS 16
#define HTTPD_STREAM_CHANNELS 2
extern struct httpd_module httpd_dacp;
extern struct httpd_module httpd_daap;
extern struct httpd_module httpd_jsonapi;
@ -93,6 +89,7 @@ static struct httpd_module *httpd_modules[] = {
struct content_type_map {
char *ext;
enum transcode_profile profile;
char *ctype;
};
@ -106,21 +103,25 @@ struct stream_ctx {
off_t offset;
off_t start_offset;
off_t end_offset;
int marked;
bool no_register_playback;
struct transcode_ctx *xcode;
};
static const struct content_type_map ext2ctype[] =
{
{ ".html", "text/html; charset=utf-8" },
{ ".xml", "text/xml; charset=utf-8" },
{ ".css", "text/css; charset=utf-8" },
{ ".txt", "text/plain; charset=utf-8" },
{ ".js", "application/javascript; charset=utf-8" },
{ ".gif", "image/gif" },
{ ".ico", "image/x-ico" },
{ ".png", "image/png" },
{ NULL, NULL }
{ ".html", XCODE_NONE, "text/html; charset=utf-8" },
{ ".xml", XCODE_NONE, "text/xml; charset=utf-8" },
{ ".css", XCODE_NONE, "text/css; charset=utf-8" },
{ ".txt", XCODE_NONE, "text/plain; charset=utf-8" },
{ ".js", XCODE_NONE, "application/javascript; charset=utf-8" },
{ ".gif", XCODE_NONE, "image/gif" },
{ ".ico", XCODE_NONE, "image/x-ico" },
{ ".png", XCODE_PNG, "image/png" },
{ ".jpg", XCODE_JPEG, "image/jpeg" },
{ ".mp3", XCODE_MP3, "audio/mpeg" },
{ ".m4a", XCODE_MP4_ALAC, "audio/mp4" },
{ ".wav", XCODE_WAV, "audio/wav" },
{ NULL, XCODE_NONE, NULL }
};
static char webroot_directory[PATH_MAX];
@ -162,16 +163,85 @@ playcount_inc_cb(void *arg)
db_file_inc_playcount(*id);
}
#ifdef LASTFM
/* Callback from the worker thread (async operation as it may block) */
static void
scrobble_cb(void *arg)
{
int *id = arg;
#ifdef LASTFM
lastfm_scrobble(*id);
}
#endif
listenbrainz_scrobble(*id);
}
static const char *
content_type_from_ext(const char *ext)
{
int i;
if (!ext)
return NULL;
for (i = 0; ext2ctype[i].ext; i++)
{
if (strcmp(ext, ext2ctype[i].ext) == 0)
return ext2ctype[i].ctype;
}
return NULL;
}
static const char *
content_type_from_profile(enum transcode_profile profile)
{
int i;
if (profile == XCODE_NONE)
return NULL;
for (i = 0; ext2ctype[i].ext; i++)
{
if (profile == ext2ctype[i].profile)
return ext2ctype[i].ctype;
}
return NULL;
}
static int
basic_auth_cred_extract(char **user, char **pwd, const char *auth)
{
char *decoded = NULL;
regex_t preg = { 0 };
regmatch_t matchptr[3]; // Room for entire string, username substring and password substring
int ret;
decoded = (char *)b64_decode(NULL, auth);
if (!decoded)
goto error;
// Apple Music gives is "(dt:1):password", which we need to support even if it
// isn't according to the basic auth RFC that says the username cannot include
// a colon
ret = regcomp(&preg, "(\\(.*?\\)|[^:]*):(.*)", REG_EXTENDED);
if (ret != 0)
goto error;
ret = regexec(&preg, decoded, ARRAY_SIZE(matchptr), matchptr, 0);
if (ret != 0 || matchptr[1].rm_so == -1 || matchptr[2].rm_so == -1)
goto error;
*user = strndup(decoded + matchptr[1].rm_so, matchptr[1].rm_eo - matchptr[1].rm_so);
*pwd = strndup(decoded + matchptr[2].rm_so, matchptr[2].rm_eo - matchptr[2].rm_so);
free(decoded);
return 0;
error:
free(decoded);
return -1;
}
/* --------------------------- MODULES INTERFACE ---------------------------- */
@ -424,19 +494,16 @@ httpd_response_not_cachable(struct httpd_request *hreq)
static void
serve_file(struct httpd_request *hreq)
{
char *ext;
char path[PATH_MAX];
char deref[PATH_MAX];
char *ctype;
const char *ctype;
struct stat sb;
int fd;
int i;
uint8_t buf[4096];
bool slashed;
int ret;
/* Check authentication */
if (!httpd_admin_check_auth(hreq))
if (!httpd_request_is_authorized(hreq))
return;
ret = snprintf(path, sizeof(path), "%s%s", webroot_directory, hreq->path);
@ -544,19 +611,9 @@ serve_file(struct httpd_request *hreq)
goto out_fail;
}
ctype = "application/octet-stream";
ext = strrchr(path, '.');
if (ext)
{
for (i = 0; ext2ctype[i].ext; i++)
{
if (strcmp(ext, ext2ctype[i].ext) == 0)
{
ctype = ext2ctype[i].ctype;
break;
}
}
}
ctype = content_type_from_ext(strrchr(path, '.'));
if (!ctype)
ctype = "application/octet-stream";
httpd_header_add(hreq->out_headers, "Content-Type", ctype);
@ -570,7 +627,6 @@ serve_file(struct httpd_request *hreq)
close(fd);
}
/* ---------------------------- STREAM HANDLING ----------------------------- */
// This will triggered in a httpd thread, but since the reading may be in a
@ -612,15 +668,13 @@ stream_end(struct stream_ctx *st)
static void
stream_end_register(struct stream_ctx *st)
{
if (!st->marked
if (!st->no_register_playback
&& (st->stream_size > ((st->size * 50) / 100))
&& (st->offset > ((st->size * 80) / 100)))
{
st->marked = 1;
st->no_register_playback = true;
worker_execute(playcount_inc_cb, &st->id, sizeof(int), 0);
#ifdef LASTFM
worker_execute(scrobble_cb, &st->id, sizeof(int), 1);
#endif
}
}
@ -643,6 +697,9 @@ stream_new(struct media_file_info *mfi, struct httpd_request *hreq, event_callba
event_active(st->ev, 0, 0);
if (httpd_query_value_find(hreq->query, "no_register_playback"))
st->no_register_playback = true;
st->id = mfi->id;
st->hreq = hreq;
return st;
@ -653,10 +710,19 @@ stream_new(struct media_file_info *mfi, struct httpd_request *hreq, event_callba
}
static struct stream_ctx *
stream_new_transcode(struct media_file_info *mfi, struct httpd_request *hreq, int64_t offset, int64_t end_offset, event_callback_fn stream_cb)
stream_new_transcode(struct media_file_info *mfi, enum transcode_profile profile, struct httpd_request *hreq,
int64_t offset, int64_t end_offset, event_callback_fn stream_cb)
{
struct transcode_decode_setup_args decode_args = { 0 };
struct transcode_encode_setup_args encode_args = { 0 };
struct media_quality quality = { 0 };
struct evbuffer *prepared_header = NULL;
struct stream_ctx *st;
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, 0 };
int cached;
int ret;
// We use source sample rate etc, but for MP3 we must set a bit rate
quality.bit_rate = 1000 * cfg_getint(cfg_getsec(cfg, "streaming"), "bit_rate");
st = stream_new(mfi, hreq, stream_cb);
if (!st)
@ -664,7 +730,27 @@ stream_new_transcode(struct media_file_info *mfi, struct httpd_request *hreq, in
goto error;
}
st->xcode = transcode_setup(XCODE_PCM16_HEADER, &quality, mfi->data_kind, mfi->path, mfi->song_length, &st->size);
if (profile == XCODE_MP4_ALAC)
{
CHECK_NULL(L_HTTPD, prepared_header = evbuffer_new());
ret = cache_xcode_header_get(prepared_header, &cached, mfi->id, "mp4");
if (ret < 0 || !cached) // Error or not found
{
evbuffer_free(prepared_header);
prepared_header = NULL;
}
}
decode_args.profile = profile;
decode_args.is_http = (mfi->data_kind == DATA_KIND_HTTP);
decode_args.path = mfi->path;
decode_args.len_ms = mfi->song_length;
encode_args.profile = profile;
encode_args.quality = &quality;
encode_args.prepared_header = prepared_header;
st->xcode = transcode_setup(decode_args, encode_args);
if (!st->xcode)
{
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
@ -673,15 +759,28 @@ stream_new_transcode(struct media_file_info *mfi, struct httpd_request *hreq, in
goto error;
}
st->size = transcode_encode_query(st->xcode->encode_ctx, "estimated_size");
if (st->size < 0)
{
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, could not determine estimated size\n");
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
goto error;
}
st->stream_size = st->size - offset;
if (end_offset > 0)
st->stream_size -= (st->size - end_offset);
st->start_offset = offset;
if (prepared_header)
evbuffer_free(prepared_header);
return st;
error:
if (prepared_header)
evbuffer_free(prepared_header);
stream_free(st);
return NULL;
}
@ -821,7 +920,7 @@ stream_chunk_raw_cb(int fd, short event, void *arg)
if (ret == 0)
DPRINTF(E_INFO, L_HTTPD, "Done streaming file id %d\n", st->id);
else
DPRINTF(E_LOG, L_HTTPD, "Streaming error, file id %d\n", st->id);
DPRINTF(E_LOG, L_HTTPD, "Read error, file id %d: %s\n", st->id, strerror(errno));
stream_end(st);
return;
@ -845,6 +944,39 @@ stream_fail_cb(void *arg)
}
/* -------------------------- SPEAKER/CACHE HANDLING ------------------------ */
// Thread: player (must not block)
static void
speaker_enum_cb(struct player_speaker_info *spk, void *arg)
{
bool *want_mp4 = arg;
*want_mp4 = *want_mp4 || (spk->format == MEDIA_FORMAT_ALAC && strcmp(spk->output_type, "RCP/SoundBridge") == 0);
}
// Thread: worker
static void
speaker_update_handler_cb(void *arg)
{
const char *prefer_format = cfg_getstr(cfg_getsec(cfg, "library"), "prefer_format");
bool want_mp4;
want_mp4 = (prefer_format && (strcmp(prefer_format, "alac") == 0));
if (!want_mp4)
player_speaker_enumerate(speaker_enum_cb, &want_mp4);
cache_xcode_toggle(want_mp4);
}
// Thread: player (must not block)
static void
httpd_speaker_update_handler(short event_mask, void *ctx)
{
worker_execute(speaker_update_handler_cb, NULL, 0, 0);
}
/* ---------------------------- REQUEST CALLBACKS --------------------------- */
// Worker thread, invoked by request_cb() below
@ -853,9 +985,7 @@ request_async_cb(void *arg)
{
struct httpd_request *hreq = *(struct httpd_request **)arg;
#ifdef HAVE_SYSCALL
DPRINTF(E_DBG, hreq->module->logdomain, "%s request '%s' in worker thread %ld\n", hreq->module->name, hreq->uri, syscall(SYS_gettid));
#endif
DPRINTF(E_DBG, hreq->module->logdomain, "%s request '%s'\n", hreq->module->name, hreq->uri);
// Some handlers require an evbase to schedule events
hreq->evbase = worker_evbase_get();
@ -913,12 +1043,14 @@ httpd_stream_file(struct httpd_request *hreq, int id)
{
struct media_file_info *mfi = NULL;
struct stream_ctx *st = NULL;
enum transcode_profile profile;
enum transcode_profile spk_profile;
const char *param;
const char *param_end;
const char *ctype;
char buf[64];
int64_t offset = 0;
int64_t end_offset = 0;
int transcode;
int ret;
param = httpd_header_find(hreq->in_headers, "Range");
@ -973,18 +1105,33 @@ httpd_stream_file(struct httpd_request *hreq, int id)
}
param = httpd_header_find(hreq->in_headers, "Accept-Codecs");
profile = transcode_needed(hreq->user_agent, param, mfi->codectype);
if (profile == XCODE_UNKNOWN)
{
DPRINTF(E_LOG, L_HTTPD, "Could not serve '%s' to client, unable to determine output format\n", mfi->path);
transcode = transcode_needed(hreq->user_agent, param, mfi->codectype);
if (transcode)
httpd_send_error(hreq, HTTP_INTERNAL, "Cannot stream, unable to determine output format");
goto error;
}
if (profile != XCODE_NONE)
{
DPRINTF(E_INFO, L_HTTPD, "Preparing to transcode %s\n", mfi->path);
st = stream_new_transcode(mfi, hreq, offset, end_offset, stream_chunk_xcode_cb);
spk_profile = httpd_xcode_profile_get(hreq);
if (spk_profile != XCODE_NONE)
profile = spk_profile;
st = stream_new_transcode(mfi, profile, hreq, offset, end_offset, stream_chunk_xcode_cb);
if (!st)
goto error;
ctype = content_type_from_profile(profile);
if (!ctype)
goto error;
if (!httpd_header_find(hreq->out_headers, "Content-Type"))
httpd_header_add(hreq->out_headers, "Content-Type", "audio/wav");
httpd_header_add(hreq->out_headers, "Content-Type", ctype);
}
else
{
@ -1027,7 +1174,7 @@ httpd_stream_file(struct httpd_request *hreq, int id)
// If we are not decoding, send the Content-Length. We don't do that if we
// are decoding because we can only guesstimate the size in this case and
// the error margin is unknown and variable.
if (!transcode)
if (profile == XCODE_NONE)
{
ret = snprintf(buf, sizeof(buf), "%" PRIi64, (int64_t)st->size);
if ((ret < 0) || (ret >= sizeof(buf)))
@ -1059,7 +1206,7 @@ httpd_stream_file(struct httpd_request *hreq, int id)
}
#ifdef HAVE_POSIX_FADVISE
if (!transcode)
if (profile == XCODE_NONE)
{
// Hint the OS
if ( (ret = posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_WILLNEED)) != 0 ||
@ -1081,6 +1228,39 @@ httpd_stream_file(struct httpd_request *hreq, int id)
free_mfi(mfi, 0);
}
// Returns enum transcode_profile, but is just declared with int so we don't
// need to include transcode.h in httpd_internal.h
int
httpd_xcode_profile_get(struct httpd_request *hreq)
{
struct player_speaker_info spk;
int ret;
// No peer address if the function is called from httpd_daap.c when the DAAP
// cache is being updated
if (!hreq->peer_address || !hreq->user_agent)
return XCODE_NONE;
// A Roku Soundbridge may also be RCP device/speaker for which the user may
// have set a prefered streaming format, but in all other cases we don't use
// speaker configuration (so caller will let transcode_needed decide)
if (strncmp(hreq->user_agent, "Roku", strlen("Roku")) != 0)
return XCODE_NONE;
ret = player_speaker_get_byaddress(&spk, hreq->peer_address);
if (ret < 0)
return XCODE_NONE;
if (spk.format == MEDIA_FORMAT_WAV)
return XCODE_WAV;
if (spk.format == MEDIA_FORMAT_MP3)
return XCODE_MP3;
if (spk.format == MEDIA_FORMAT_ALAC)
return XCODE_MP4_ALAC;
return XCODE_NONE;
}
struct evbuffer *
httpd_gzip_deflate(struct evbuffer *in)
{
@ -1148,7 +1328,6 @@ httpd_gzip_deflate(struct evbuffer *in)
return NULL;
}
// The httpd_send functions below can be called from a worker thread (with
// hreq->is_async) or directly from the httpd thread. In the former case, they
// will command sending from the httpd thread, since it is not safe to access
@ -1225,12 +1404,18 @@ httpd_send_error(struct httpd_request *hreq, int error, const char *reason)
}
bool
httpd_admin_check_auth(struct httpd_request *hreq)
httpd_request_is_trusted(struct httpd_request *hreq)
{
return httpd_backend_peer_is_trusted(hreq->backend);
}
bool
httpd_request_is_authorized(struct httpd_request *hreq)
{
const char *passwd;
int ret;
if (net_peer_address_is_trusted(hreq->peer_address))
if (httpd_request_is_trusted(hreq))
return true;
passwd = cfg_getstr(cfg_getsec(cfg, "general"), "admin_password");
@ -1284,26 +1469,14 @@ httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passw
auth += strlen("Basic ");
authuser = (char *)b64_decode(NULL, auth);
if (!authuser)
{
DPRINTF(E_LOG, L_HTTPD, "Could not decode Authentication header\n");
goto need_auth;
}
authpwd = strchr(authuser, ':');
if (!authpwd)
ret = basic_auth_cred_extract(&authuser, &authpwd, auth);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTPD, "Malformed Authentication header\n");
free(authuser);
goto need_auth;
}
*authpwd = '\0';
authpwd++;
if (user)
{
if (strcmp(user, authuser) != 0)
@ -1311,6 +1484,7 @@ httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passw
DPRINTF(E_LOG, L_HTTPD, "Username mismatch\n");
free(authuser);
free(authpwd);
goto need_auth;
}
}
@ -1320,11 +1494,12 @@ httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passw
DPRINTF(E_LOG, L_HTTPD, "Bad password\n");
free(authuser);
free(authpwd);
goto need_auth;
}
free(authuser);
free(authpwd);
return 0;
need_auth:
@ -1457,6 +1632,10 @@ httpd_init(const char *webroot)
goto error;
}
// We need to know about speaker format changes so we can ask the cache to
// start preparing headers for mp4/alac if selected
listener_add(httpd_speaker_update_handler, LISTENER_SPEAKER, NULL);
return 0;
error:
@ -1468,6 +1647,8 @@ httpd_init(const char *webroot)
void
httpd_deinit(void)
{
listener_remove(httpd_speaker_update_handler);
// Give modules a chance to hang up connections nicely
modules_deinit();

View File

@ -74,20 +74,20 @@ response_process(struct httpd_request *hreq, int format)
static int
artworkapi_reply_nowplaying(struct httpd_request *hreq)
{
struct player_status status;
uint32_t max_w;
uint32_t max_h;
uint32_t id;
int ret;
ret = request_process(hreq, &max_w, &max_h);
if (ret != 0)
return ret;
ret = player_playing_now(&id);
if (ret != 0)
player_get_status(&status);
if (status.status == PLAY_STOPPED)
return HTTP_NOTFOUND;
ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
ret = artwork_get_by_queue_item_id(hreq->out_body, status.item_id, max_w, max_h, 0);
return response_process(hreq, ret);
}
@ -108,7 +108,7 @@ artworkapi_reply_item(struct httpd_request *hreq)
if (ret != 0)
return HTTP_BADREQUEST;
ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
ret = artwork_get_by_file_id(hreq->out_body, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
@ -129,7 +129,7 @@ artworkapi_reply_group(struct httpd_request *hreq)
if (ret != 0)
return HTTP_BADREQUEST;
ret = artwork_get_group(hreq->out_body, id, max_w, max_h, 0);
ret = artwork_get_by_group_id(hreq->out_body, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
@ -150,7 +150,7 @@ artworkapi_request(struct httpd_request *hreq)
{
int status_code;
if (!httpd_admin_check_auth(hreq))
if (!httpd_request_is_authorized(hreq))
return;
if (!hreq->handler)

View File

@ -693,7 +693,7 @@ daap_request_authorize(struct httpd_request *hreq)
char *passwd;
int ret;
if (net_peer_address_is_trusted(hreq->peer_address))
if (httpd_request_is_trusted(hreq))
return 0;
// Regular DAAP clients like iTunes will login with /login, and we will reply
@ -898,7 +898,7 @@ daap_reply_login(struct httpd_request *hreq)
CHECK_ERR(L_DAAP, evbuffer_expand(hreq->out_body, 32));
param = httpd_query_value_find(hreq->query, "pairing-guid");
if (param && !net_peer_address_is_trusted(hreq->peer_address))
if (param && !httpd_request_is_trusted(hreq))
{
if (strlen(param) < 3)
{
@ -1146,14 +1146,17 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
const struct dmap_field **meta = NULL;
struct sort_ctx *sctx;
const char *param;
const char *client_codecs;
const char *accept_codecs;
const char *tag;
char *last_codectype;
size_t len;
enum transcode_profile spk_profile;
enum transcode_profile profile;
struct transcode_metadata_string xcode_metadata;
struct media_quality quality = { 0 };
uint32_t len_ms;
int nmeta = 0;
int sort_headers;
int nsongs;
int transcode;
int ret;
DPRINTF(E_DBG, L_DAAP, "Fetching song list for playlist %d\n", playlist);
@ -1214,37 +1217,50 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
goto error;
}
client_codecs = NULL;
accept_codecs = NULL;
if (!s->is_remote && hreq->in_headers)
{
client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
accept_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
}
spk_profile = httpd_xcode_profile_get(hreq);
DPRINTF(E_DBG, L_DAAP, "Speaker check of '%s' (codecs '%s') returned %d\n", hreq->user_agent, accept_codecs, spk_profile);
nsongs = 0;
last_codectype = NULL;
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
nsongs++;
if (!dbmfi.codectype)
// Not sure if the is_remote path is really needed. Note that if you
// change the below you might need to do the same in rsp_reply_playlist()
profile = s->is_remote ? XCODE_WAV : transcode_needed(hreq->user_agent, accept_codecs, dbmfi.codectype);
if (profile == XCODE_UNKNOWN)
{
DPRINTF(E_LOG, L_DAAP, "Cannot transcode '%s', codec type is unknown\n", dbmfi.fname);
transcode = 0;
}
else if (s->is_remote)
else if (profile != XCODE_NONE)
{
transcode = 1;
}
else if (!last_codectype || (strcmp(last_codectype, dbmfi.codectype) != 0))
{
transcode = transcode_needed(hreq->user_agent, client_codecs, dbmfi.codectype);
if (spk_profile != XCODE_NONE)
profile = spk_profile;
free(last_codectype);
last_codectype = strdup(dbmfi.codectype);
if (safe_atou32(dbmfi.song_length, &len_ms) < 0)
len_ms = 3 * 60 * 1000; // just a fallback default
safe_atoi32(dbmfi.samplerate, &quality.sample_rate);
safe_atoi32(dbmfi.bits_per_sample, &quality.bits_per_sample);
safe_atoi32(dbmfi.channels, &quality.channels);
quality.bit_rate = cfg_getint(cfg_getsec(cfg, "streaming"), "bit_rate");
transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms);
dbmfi.type = xcode_metadata.type;
dbmfi.codectype = xcode_metadata.codectype;
dbmfi.description = xcode_metadata.description;
dbmfi.file_size = xcode_metadata.file_size;
dbmfi.bitrate = xcode_metadata.bitrate;
}
ret = dmap_encode_file_metadata(songlist, song, &dbmfi, meta, nmeta, sort_headers, transcode);
ret = dmap_encode_file_metadata(songlist, song, &dbmfi, meta, nmeta, sort_headers);
if (ret < 0)
{
DPRINTF(E_LOG, L_DAAP, "Failed to encode song metadata\n");
@ -1270,7 +1286,6 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
DPRINTF(E_DBG, L_DAAP, "Done with song list, %d songs\n", nsongs);
free(last_codectype);
db_query_end(&qp);
if (ret == -100)
@ -1971,9 +1986,9 @@ daap_reply_extra_data(struct httpd_request *hreq)
}
if (strcmp(hreq->path_parts[2], "groups") == 0)
ret = artwork_get_group(hreq->out_body, id, max_w, max_h, 0);
ret = artwork_get_by_group_id(hreq->out_body, id, max_w, max_h, 0);
else if (strcmp(hreq->path_parts[2], "items") == 0)
ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
ret = artwork_get_by_file_id(hreq->out_body, id, max_w, max_h, 0);
len = evbuffer_get_length(hreq->out_body);
@ -2264,15 +2279,15 @@ daap_request(struct httpd_request *hreq)
ret = hreq->handler(hreq);
daap_reply_send(hreq, ret);
clock_gettime(CLOCK_MONOTONIC, &end);
msec = (end.tv_sec * 1000 + end.tv_nsec / 1000000) - (start.tv_sec * 1000 + start.tv_nsec / 1000000);
DPRINTF(E_DBG, L_DAAP, "DAAP request handled in %d milliseconds\n", msec);
if (ret == DAAP_REPLY_OK && msec > cache_daap_threshold() && hreq->user_agent)
if (ret == DAAP_REPLY_OK && msec > cache_daap_threshold_get() && hreq->user_agent)
cache_daap_add(hreq->uri, hreq->user_agent, ((struct daap_session *)hreq->extra_data)->is_remote, msec);
daap_reply_send(hreq, ret); // hreq is deallocted
}
int

View File

@ -40,6 +40,7 @@
#include "conffile.h"
#include "artwork.h"
#include "dmap_common.h"
#include "library.h"
#include "db.h"
#include "player.h"
#include "listener.h"
@ -163,7 +164,7 @@ dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_
* FIXME: Giving the client invalid ids on purpose is hardly ideal, but the
* clients don't seem to use these ids for anything other than rating.
*/
if (queue_item->data_kind == DATA_KIND_HTTP || queue_item->data_kind == DATA_KIND_PIPE)
if (queue_item->data_kind == DATA_KIND_HTTP || queue_item->data_kind == DATA_KIND_PIPE || queue_item->file_id == DB_MEDIA_FILE_NON_PERSISTENT_ID)
{
// Could also use queue_item->queue_version, but it changes a bit too much
// leading to Remote reloading too much
@ -599,7 +600,7 @@ dacp_request_authorize(struct httpd_request *hreq)
int32_t id;
int ret;
if (net_peer_address_is_trusted(hreq->peer_address))
if (httpd_request_is_trusted(hreq))
return 0;
param = httpd_query_value_find(hreq->query, "session-id");
@ -786,7 +787,7 @@ update_fail_cb(void *arg)
/* Thread: player */
static void
dacp_playstatus_update_handler(short event_mask)
dacp_playstatus_update_handler(short event_mask, void *ctx)
{
struct dacp_update_request *ur;
@ -1106,31 +1107,7 @@ dacp_propset_userrating(const char *value, struct httpd_request *hreq)
return;
}
ret = db_file_rating_update_byid(itemid, rating);
/* If no mfi, it may be because we sent an invalid nowplaying itemid. In this
* case request the real one from the player and default to that.
*/
if (ret == 0)
{
DPRINTF(E_WARN, L_DACP, "Invalid id %d for rating, defaulting to player id\n", itemid);
ret = player_playing_now(&itemid);
if (ret < 0)
{
DPRINTF(E_WARN, L_DACP, "Could not find an id for rating\n");
return;
}
ret = db_file_rating_update_byid(itemid, rating);
if (ret <= 0)
{
DPRINTF(E_WARN, L_DACP, "Could not find an id for rating\n");
return;
}
}
library_item_attrib_save(itemid, LIBRARY_ATTRIB_RATING, rating);
}
@ -1397,6 +1374,7 @@ dacp_reply_playspec(struct httpd_request *hreq)
const char *shuffle;
uint32_t plid;
uint32_t id;
struct query_params qp = { 0 };
struct db_queue_item *queue_item = NULL;
int ret;
@ -1482,11 +1460,10 @@ dacp_reply_playspec(struct httpd_request *hreq)
db_queue_clear(0);
if (plid > 0)
ret = db_queue_add_by_playlistid(plid, status.shuffle, status.item_id, -1, NULL, NULL);
else if (id > 0)
ret = db_queue_add_by_fileid(id, status.shuffle, status.item_id, -1, NULL, NULL);
qp.type = (plid > 0) ? Q_PLITEMS : Q_ITEMS;
qp.id = (plid > 0) ? plid : id;
ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id, -1, NULL, NULL);
if (ret < 0)
{
DPRINTF(E_LOG, L_DACP, "Could not build song queue from playlist %d\n", plid);
@ -1875,15 +1852,16 @@ dacp_reply_playqueueedit_clear(struct httpd_request *hreq)
const char *param;
struct player_status status;
param = httpd_query_value_find(hreq->query, "mode");
/*
* The mode parameter contains the playlist to be cleared.
* If mode=0x68697374 (hex representation of the ascii string "hist") clear the history,
* otherwise the current playlist.
*/
if (strcmp(param,"0x68697374") == 0)
player_queue_clear_history();
param = httpd_query_value_find(hreq->query, "mode");
if (param && strcmp(param,"0x68697374") == 0)
{
player_queue_clear_history();
}
else
{
player_get_status(&status);
@ -1916,6 +1894,7 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq)
const char *querymodifier;
const char *sort;
const char *param;
const char *ptr;
char modifiedquery[32];
int mode;
int plid;
@ -1977,7 +1956,8 @@ dacp_reply_playqueueedit_add(struct httpd_request *hreq)
else
{
// Modify the query: Take the id from the editquery and use it as a queuefilter playlist id
ret = safe_atoi32(strchr(editquery, ':') + 1, &plid);
ptr = strchr(editquery, ':');
ret = ptr ? safe_atoi32(ptr + 1, &plid) : -1;
if (ret < 0)
{
DPRINTF(E_LOG, L_DACP, "Invalid playlist id in request: %s\n", editquery);
@ -2051,38 +2031,44 @@ dacp_reply_playqueueedit_move(struct httpd_request *hreq)
struct player_status status;
int ret;
const char *param;
const char *ptr;
int src;
int dst;
param = httpd_query_value_find(hreq->query, "edit-params");
if (param)
{
ret = safe_atoi32(strchr(param, ':') + 1, &src);
if (ret < 0)
{
DPRINTF(E_LOG, L_DACP, "Invalid edit-params move-from value in playqueue-edit request\n");
if (!param)
goto out;
dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
ptr = strchr(param, ':');
if (!ptr)
goto error;
ret = safe_atoi32(strchr(param, ',') + 1, &dst);
if (ret < 0)
{
DPRINTF(E_LOG, L_DACP, "Invalid edit-params move-to value in playqueue-edit request\n");
ret = safe_atoi32(ptr + 1, &src);
if (ret < 0)
goto error;
dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
ptr = strchr(param, ',');
if (!ptr)
goto error;
player_get_status(&status);
db_queue_move_byposrelativetoitem(src, dst, status.item_id, status.shuffle);
}
ret = safe_atoi32(ptr + 1, &dst);
if (ret < 0)
goto error;
player_get_status(&status);
db_queue_move_byposrelativetoitem(src, dst, status.item_id, status.shuffle);
out:
/* 204 No Content is the canonical reply */
httpd_send_reply(hreq, HTTP_NOCONTENT, "No Content", HTTPD_SEND_NO_GZIP);
return 0;
error:
DPRINTF(E_LOG, L_DACP, "Invalid edit-params in playqueue-edit request: '%s'\n", param);
dacp_send_error(hreq, "cacr", "Invalid request");
return -1;
}
static int
@ -2271,11 +2257,11 @@ dacp_reply_playstatusupdate(struct httpd_request *hreq)
static int
dacp_reply_nowplayingartwork(struct httpd_request *hreq)
{
struct player_status status;
char clen[32];
const char *param;
char *ctype;
size_t len;
uint32_t id;
int max_w;
int max_h;
int ret;
@ -2312,11 +2298,11 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq)
goto error;
}
ret = player_playing_now(&id);
if (ret < 0)
player_get_status(&status);
if (status.status == PLAY_STOPPED)
goto no_artwork;
ret = artwork_get_item(hreq->out_body, id, max_w, max_h, 0);
ret = artwork_get_by_queue_item_id(hreq->out_body, status.item_id, max_w, max_h, 0);
len = evbuffer_get_length(hreq->out_body);
switch (ret)
@ -2560,8 +2546,7 @@ dacp_reply_setspeakers(struct httpd_request *hreq)
}
nspk = 1;
ptr = param;
while ((ptr = strchr(ptr + 1, ',')))
for (ptr = param; ptr; ptr = strchr(ptr + 1, ','))
nspk++;
CHECK_NULL(L_DACP, ids = calloc((nspk + 1), sizeof(uint64_t)));
@ -2841,7 +2826,7 @@ dacp_init(void)
CHECK_ERR(L_DACP, mutex_init(&update_request_lck));
update_current_rev = 2;
listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER | LISTENER_VOLUME | LISTENER_QUEUE);
listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER | LISTENER_VOLUME | LISTENER_QUEUE, NULL);
return 0;
}

View File

@ -206,6 +206,9 @@ struct httpd_request {
void
httpd_stream_file(struct httpd_request *hreq, int id);
int
httpd_xcode_profile_get(struct httpd_request *hreq);
void
httpd_request_handler_set(struct httpd_request *hreq);
@ -224,7 +227,8 @@ httpd_response_not_cachable(struct httpd_request *hreq);
* may direct it not to. It will set CORS headers as appropriate. Should be
* thread safe.
*
* @in req The http request struct
* @in hreq The http request struct. NOTE: is automatically deallocated if
* this is the final reply.
* @in code HTTP code, e.g. 200
* @in reason A brief explanation of the error - if NULL the standard meaning
of the error code will be used
@ -248,7 +252,8 @@ httpd_send_reply_end(struct httpd_request *hreq);
* which is not possible with evhttp_send_error, because it clears the headers.
* Should be thread safe.
*
* @in req The http request struct
* @in hreq The http request struct. NOTE: is automatically deallocated if
* this is the final reply.
* @in error HTTP code, e.g. 200
* @in reason A brief explanation of the error - if NULL the standard meaning
of the error code will be used
@ -256,12 +261,18 @@ httpd_send_reply_end(struct httpd_request *hreq);
void
httpd_send_error(struct httpd_request *hreq, int error, const char *reason);
void
httpd_redirect_to(struct httpd_request *hreq, const char *path);
/*
* The request either came from a trusted peer (based on ip address) checked by
* httpd_request_is_trusted() or was WWW-authenticated via httpd_basic_auth()
*/
bool
httpd_admin_check_auth(struct httpd_request *hreq);
httpd_request_is_authorized(struct httpd_request *hreq);
bool
httpd_request_is_trusted(struct httpd_request *hreq);
int
httpd_basic_auth(struct httpd_request *hreq, const char *user, const char *passwd, const char *realm);
@ -342,6 +353,9 @@ httpd_backend_input_buffer_get(httpd_backend *backend);
int
httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data);
bool
httpd_backend_peer_is_trusted(httpd_backend *backend);
int
httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend);

View File

@ -47,6 +47,7 @@
# include "lastfm.h"
#endif
#include "library.h"
#include "listenbrainz.h"
#include "logger.h"
#include "misc.h"
#include "misc_json.h"
@ -59,6 +60,22 @@
# include "inputs/spotify.h"
#endif
struct track_attribs
{
enum library_attrib type;
const char *name;
};
// Currently these must all be uint32
static const struct track_attribs track_attribs[] =
{
{ LIBRARY_ATTRIB_PLAY_COUNT, "play_count", },
{ LIBRARY_ATTRIB_SKIP_COUNT, "skip_count", },
{ LIBRARY_ATTRIB_TIME_PLAYED, "time_played", },
{ LIBRARY_ATTRIB_TIME_SKIPPED, "time_skipped", },
{ LIBRARY_ATTRIB_RATING, "rating", },
{ LIBRARY_ATTRIB_USERMARK, "usermark", },
};
static bool allow_modifying_stored_playlists;
static char *default_playlist_directory;
@ -322,7 +339,7 @@ track_to_json(struct db_media_file_info *dbmfi)
safe_json_add_string(item, "path", dbmfi->path);
ret = snprintf(uri, sizeof(uri), "%s:%s:%s", "library", "track", dbmfi->id);
ret = snprintf(uri, sizeof(uri), "library:track:%s", dbmfi->id);
if (ret < sizeof(uri))
json_object_object_add(item, "uri", json_object_new_string(uri));
@ -330,28 +347,10 @@ track_to_json(struct db_media_file_info *dbmfi)
if (ret < sizeof(artwork_url))
json_object_object_add(item, "artwork_url", json_object_new_string(artwork_url));
safe_json_add_string(item, "lyrics", dbmfi->lyrics);
return item;
}
// TODO Only partially implemented. A full implementation should use a mapping
// table, which should also be used above in track_to_json(). It should also
// return errors if there are incorrect/mispelled fields, but not sure how to
// walk a json object with json-c.
static int
json_to_track(struct media_file_info *mfi, json_object *json)
{
if (jparse_contains_key(json, "id", json_type_int))
mfi->id = jparse_int_from_obj(json, "id");
if (jparse_contains_key(json, "usermark", json_type_int))
mfi->usermark = jparse_int_from_obj(json, "usermark");
if (jparse_contains_key(json, "rating", json_type_int))
mfi->rating = jparse_int_from_obj(json, "rating");
if (jparse_contains_key(json, "play_count", json_type_int))
mfi->play_count = jparse_int_from_obj(json, "play_count");
return HTTP_OK;
}
static json_object *
playlist_to_json(struct db_playlist_info *dbpli)
{
@ -543,7 +542,7 @@ fetch_artist(bool *notfound, const char *artist_id)
if ((ret = db_query_fetch_group(&dbgri, &query_params)) == 0)
{
artist = artist_to_json(&dbgri);
notfound = false;
*notfound = false;
}
error:
@ -1285,8 +1284,6 @@ jsonapi_reply_spotify(struct httpd_request *hreq)
CHECK_NULL(L_WEB, jreply = json_object_new_object());
#ifdef SPOTIFY
int httpd_port;
char redirect_uri[256];
char *oauth_uri;
struct spotify_status sp_status;
struct spotifywebapi_status_info webapi_info;
@ -1294,10 +1291,7 @@ jsonapi_reply_spotify(struct httpd_request *hreq)
json_object_object_add(jreply, "enabled", json_object_new_boolean(true));
httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
snprintf(redirect_uri, sizeof(redirect_uri), "http://owntone.local:%d/oauth/spotify", httpd_port);
oauth_uri = spotifywebapi_oauth_uri_get(redirect_uri);
oauth_uri = spotifywebapi_oauth_uri_get();
if (!oauth_uri)
{
DPRINTF(E_LOG, L_WEB, "Cannot display Spotify oauth interface (http_form_uriencode() failed)\n");
@ -1319,6 +1313,8 @@ jsonapi_reply_spotify(struct httpd_request *hreq)
safe_json_add_string(jreply, "webapi_country", webapi_info.country);
safe_json_add_string(jreply, "webapi_granted_scope", webapi_info.granted_scope);
safe_json_add_string(jreply, "webapi_required_scope", webapi_info.required_scope);
safe_json_add_string(jreply, "webapi_client_id", webapi_info.client_id);
safe_json_add_string(jreply, "webapi_client_secret", webapi_info.client_secret);
spotifywebapi_access_token_get(&webapi_token);
safe_json_add_string(jreply, "webapi_token", webapi_token.token);
@ -1449,6 +1445,77 @@ jsonapi_reply_lastfm_logout(struct httpd_request *hreq)
return HTTP_NOCONTENT;
}
static int
jsonapi_reply_listenbrainz(struct httpd_request *hreq)
{
struct listenbrainz_status status;
json_object *jreply;
listenbrainz_status_get(&status);
CHECK_NULL(L_WEB, jreply = json_object_new_object());
json_object_object_add(jreply, "enabled", json_object_new_boolean(!status.disabled));
json_object_object_add(jreply, "token_valid", json_object_new_boolean(status.token_valid));
if (status.user_name)
json_object_object_add(jreply, "user_name", json_object_new_string(status.user_name));
if (status.message)
json_object_object_add(jreply, "message", json_object_new_string(status.message));
CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
listenbrainz_status_free(&status, true);
return HTTP_OK;
}
static int
jsonapi_reply_listenbrainz_token_add(struct httpd_request *hreq)
{
json_object *request;
const char *token;
int ret;
request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
return HTTP_BADREQUEST;
}
token = jparse_str_from_obj(request, "token");
ret = listenbrainz_token_set(token);
jparse_free(request);
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Failed to set ListenBrainz token\n");
return HTTP_INTERNAL;
}
return HTTP_NOCONTENT;
}
static int
jsonapi_reply_listenbrainz_token_delete(struct httpd_request *hreq)
{
int ret;
ret = listenbrainz_token_delete();
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Failed to delete ListenBrainz token\n");
return HTTP_INTERNAL;
}
return HTTP_NOCONTENT;
}
/*
* Kicks off pairing of a daap/dacp client
*
@ -1544,10 +1611,19 @@ static json_object *
speaker_to_json(struct player_speaker_info *spk)
{
json_object *output;
json_object *supported_formats;
char output_id[21];
enum media_format format;
output = json_object_new_object();
supported_formats = json_object_new_array();
for (format = MEDIA_FORMAT_FIRST; format <= MEDIA_FORMAT_LAST; format = MEDIA_FORMAT_NEXT(format))
{
if (format & spk->supported_formats)
json_object_array_add(supported_formats, json_object_new_string(media_format_to_string(format)));
}
snprintf(output_id, sizeof(output_id), "%" PRIu64, spk->id);
json_object_object_add(output, "id", json_object_new_string(output_id));
json_object_object_add(output, "name", json_object_new_string(spk->name));
@ -1557,6 +1633,8 @@ speaker_to_json(struct player_speaker_info *spk)
json_object_object_add(output, "requires_auth", json_object_new_boolean(spk->requires_auth));
json_object_object_add(output, "needs_auth_key", json_object_new_boolean(spk->needs_auth_key));
json_object_object_add(output, "volume", json_object_new_int(spk->absvol));
json_object_object_add(output, "format", json_object_new_string(media_format_to_string(spk->format)));
json_object_object_add(output, "supported_formats", supported_formats);
return output;
}
@ -1616,58 +1694,66 @@ static int
jsonapi_reply_outputs_put_byid(struct httpd_request *hreq)
{
uint64_t output_id;
json_object* request;
json_object *request = NULL;
bool selected;
int volume;
const char *pin;
const char *format;
int ret;
ret = safe_atou64(hreq->path_parts[2], &output_id);
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->path);
return HTTP_BADREQUEST;
goto error;
}
request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
return HTTP_BADREQUEST;
goto error;
}
ret = 0;
if (jparse_contains_key(request, "selected", json_type_boolean))
{
selected = jparse_bool_from_obj(request, "selected");
if (selected)
ret = player_speaker_enable(output_id);
else
ret = player_speaker_disable(output_id);
ret = selected ? player_speaker_enable(output_id) : player_speaker_disable(output_id);
if (ret < 0)
goto error;
}
if (ret == 0 && jparse_contains_key(request, "volume", json_type_int))
if (jparse_contains_key(request, "volume", json_type_int))
{
volume = jparse_int_from_obj(request, "volume");
ret = player_volume_setabs_speaker(output_id, volume);
if (ret < 0)
goto error;
}
if (ret == 0 && jparse_contains_key(request, "pin", json_type_string))
if (jparse_contains_key(request, "pin", json_type_string))
{
pin = jparse_str_from_obj(request, "pin");
if (pin)
ret = player_speaker_authorize(output_id, pin);
ret = pin ? player_speaker_authorize(output_id, pin) : 0;
if (ret < 0)
goto error;
}
if (jparse_contains_key(request, "format", json_type_string))
{
format = jparse_str_from_obj(request, "format");
ret = format ? player_speaker_format_set(output_id, media_format_from_string(format)) : 0;
if (ret < 0)
goto error;
}
jparse_free(request);
if (ret < 0)
return HTTP_INTERNAL;
return HTTP_NOCONTENT;
error:
jparse_free(request);
return HTTP_BADREQUEST;
}
/*
@ -2227,199 +2313,59 @@ queue_item_to_json(struct db_queue_item *queue_item, char shuffle)
}
static int
queue_tracks_add_artist(const char *id, int pos)
queue_tracks_add_byuris(const char *param, char shuffle, uint32_t item_id, int pos, int *total_count, int *new_item_id)
{
struct query_params query_params;
struct player_status status;
int count = 0;
int ret = 0;
memset(&query_params, 0, sizeof(struct query_params));
query_params.type = Q_ITEMS;
query_params.sort = S_ALBUM;
query_params.idx_type = I_NONE;
query_params.filter = db_mprintf("(f.songartistid = %q)", id);
player_get_status(&status);
ret = db_queue_add_by_query(&query_params, status.shuffle, status.item_id, pos, &count, NULL);
free(query_params.filter);
if (ret == 0)
return count;
return ret;
}
static int
queue_tracks_add_album(const char *id, int pos)
{
struct query_params query_params;
struct player_status status;
int count = 0;
int ret = 0;
memset(&query_params, 0, sizeof(struct query_params));
query_params.type = Q_ITEMS;
query_params.sort = S_ALBUM;
query_params.idx_type = I_NONE;
query_params.filter = db_mprintf("(f.songalbumid = %q)", id);
player_get_status(&status);
ret = db_queue_add_by_query(&query_params, status.shuffle, status.item_id, pos, &count, NULL);
free(query_params.filter);
if (ret == 0)
return count;
return ret;
}
static int
queue_tracks_add_track(const char *id, int pos)
{
struct query_params query_params;
struct player_status status;
int count = 0;
int ret = 0;
memset(&query_params, 0, sizeof(struct query_params));
query_params.type = Q_ITEMS;
query_params.sort = S_ALBUM;
query_params.idx_type = I_NONE;
query_params.filter = db_mprintf("(f.id = %q)", id);
player_get_status(&status);
ret = db_queue_add_by_query(&query_params, status.shuffle, status.item_id, pos, &count, NULL);
free(query_params.filter);
if (ret == 0)
return count;
return ret;
}
static int
queue_tracks_add_playlist(const char *id, int pos)
{
struct player_status status;
int playlist_id;
int count = 0;
char *uris;
const char *uri;
char *ptr;
int count;
int new;
int ret;
ret = safe_atoi32(id, &playlist_id);
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", id);
return HTTP_BADREQUEST;
}
player_get_status(&status);
ret = db_queue_add_by_playlistid(playlist_id, status.shuffle, status.item_id, pos, &count, NULL);
if (ret == 0)
return count;
return ret;
}
static int
queue_tracks_add_byuris(const char *param, int pos, int *total_count)
{
struct player_status status;
char *uris;
char *uri;
char *ptr;
const char *id;
int count = 0;
int ret = 0;
*total_count = 0;
*new_item_id = -1;
CHECK_NULL(L_WEB, uris = strdup(param));
uri = strtok_r(uris, ",", &ptr);
uri = strtok_r(uris, ",", &ptr);
if (!uri)
{
DPRINTF(E_LOG, L_WEB, "Empty query parameter 'uris'\n");
free(uris);
return -1;
goto error;
}
do
for (; uri; uri = strtok_r(NULL, ",", &ptr))
{
count = 0;
if (strncmp(uri, "library:artist:", strlen("library:artist:")) == 0)
ret = library_queue_item_add(uri, pos, shuffle, item_id, &count, &new);
if (ret != LIBRARY_OK)
{
id = uri + (strlen("library:artist:"));
count = queue_tracks_add_artist(id, pos);
DPRINTF(E_LOG, L_WEB, "Invalid uri '%s'\n", uri);
goto error;
}
else if (strncmp(uri, "library:album:", strlen("library:album:")) == 0)
{
id = uri + (strlen("library:album:"));
count = queue_tracks_add_album(id, pos);
}
else if (strncmp(uri, "library:track:", strlen("library:track:")) == 0)
{
id = uri + (strlen("library:track:"));
count = queue_tracks_add_track(id, pos);
}
else if (strncmp(uri, "library:playlist:", strlen("library:playlist:")) == 0)
{
id = uri + (strlen("library:playlist:"));
count = queue_tracks_add_playlist(id, pos);
}
else
{
player_get_status(&status);
ret = library_queue_item_add(uri, pos, status.shuffle, status.item_id, &count, NULL);
if (ret != LIBRARY_OK)
{
DPRINTF(E_LOG, L_WEB, "Invalid uri '%s'\n", uri);
break;
}
pos += count;
}
if (pos >= 0)
pos += count;
*total_count += count;
if (pos >= 0)
pos += count;
if (*new_item_id == -1)
*new_item_id = new;
}
while ((uri = strtok_r(NULL, ",", &ptr)));
free(uris);
return 0;
return ret;
error:
free(uris);
return -1;
}
static int
queue_tracks_add_byexpression(const char *param, int pos, int limit, int *total_count)
queue_tracks_add_byexpression(const char *param, char shuffle, uint32_t item_id, int pos, int limit, int *total_count, int *new_item_id)
{
struct query_params query_params = { .type = Q_ITEMS, .sort = S_NAME };
struct smartpl smartpl_expression = { 0 };
char *expression;
struct smartpl smartpl_expression;
struct query_params query_params;
struct player_status status;
int ret;
memset(&query_params, 0, sizeof(struct query_params));
query_params.type = Q_ITEMS;
query_params.sort = S_NAME;
memset(&smartpl_expression, 0, sizeof(struct smartpl));
expression = safe_asprintf("\"query\" { %s }", param);
ret = smartpl_query_parse_string(&smartpl_expression, expression);
free(expression);
@ -2430,18 +2376,60 @@ queue_tracks_add_byexpression(const char *param, int pos, int limit, int *total_
query_params.filter = strdup(smartpl_expression.query_where);
query_params.order = safe_strdup(smartpl_expression.order);
query_params.limit = limit > 0 ? limit : smartpl_expression.limit;
query_params.idx_type = query_params.limit > 0 ? I_FIRST : I_NONE;
free_smartpl(&smartpl_expression, 1);
player_get_status(&status);
query_params.idx_type = query_params.limit > 0 ? I_FIRST : I_NONE;
ret = db_queue_add_by_query(&query_params, status.shuffle, status.item_id, pos, total_count, NULL);
ret = db_queue_add_by_query(&query_params, shuffle, item_id, pos, total_count, new_item_id);
free_query_params(&query_params, 1);
return ret;
}
static int
create_reply_queue_tracks_add(struct evbuffer *evbuf, int count, int new_item_id, char shuffle)
{
json_object *reply = json_object_new_object();
json_object *items = json_object_new_array();
json_object *item;
struct query_params query_params = { 0 };
struct db_queue_item queue_item;
int version = 0;
int ret;
db_admin_getint(&version, DB_ADMIN_QUEUE_VERSION);
json_object_object_add(reply, "version", json_object_new_int(version));
json_object_object_add(reply, "count", json_object_new_int(count));
json_object_object_add(reply, "items", items);
ret = db_queue_enum_start(&query_params);
if (ret < 0)
goto error;
while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
{
if (queue_item.id < new_item_id)
continue;
item = queue_item_to_json(&queue_item, shuffle);
if (!item)
goto error;
json_object_array_add(items, item);
}
ret = evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(reply));
if (ret < 0)
goto error;
db_queue_enum_end(&query_params);
jparse_free(reply);
return 0;
error:
db_queue_enum_end(&query_params);
jparse_free(reply);
return -1;
}
static int
@ -2451,11 +2439,12 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
const char *param_uris;
const char *param_expression;
const char *param;
struct player_status status;
int pos;
int limit;
bool shuffle;
int total_count = 0;
json_object *reply;
int new_item_id = 0;
int ret = 0;
@ -2469,7 +2458,7 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
return HTTP_BADREQUEST;
}
DPRINTF(E_DBG, L_WEB, "Add tracks starting at position '%d\n", pos);
DPRINTF(E_DBG, L_WEB, "Add tracks starting at position %d\n", pos);
}
else
pos = -1;
@ -2500,29 +2489,25 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
player_shuffle_set(shuffle);
}
player_get_status(&status);
if (param_uris)
{
ret = queue_tracks_add_byuris(param_uris, pos, &total_count);
ret = queue_tracks_add_byuris(param_uris, status.shuffle, status.item_id, pos, &total_count, &new_item_id);
}
else
{
// This overrides the value specified in query
param = httpd_query_value_find(hreq->query, "limit");
if (param && safe_atoi32(param, &limit) == 0)
ret = queue_tracks_add_byexpression(param_expression, pos, limit, &total_count);
ret = queue_tracks_add_byexpression(param_expression, status.shuffle, status.item_id, pos, limit, &total_count, &new_item_id);
else
ret = queue_tracks_add_byexpression(param_expression, pos, -1, &total_count);
}
if (ret == 0)
{
reply = json_object_new_object();
json_object_object_add(reply, "count", json_object_new_int(total_count));
ret = evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(reply));
jparse_free(reply);
ret = queue_tracks_add_byexpression(param_expression, status.shuffle, status.item_id, pos, -1, &total_count, &new_item_id);
}
if (ret < 0)
return HTTP_INTERNAL;
ret = create_reply_queue_tracks_add(hreq->out_body, total_count, new_item_id, status.shuffle);
if (ret < 0)
return HTTP_INTERNAL;
@ -2635,6 +2620,7 @@ static int
jsonapi_reply_queue_tracks_delete(struct httpd_request *hreq)
{
uint32_t item_id;
uint32_t count;
int ret;
ret = safe_atou32(hreq->path_parts[3], &item_id);
@ -2651,6 +2637,13 @@ jsonapi_reply_queue_tracks_delete(struct httpd_request *hreq)
return HTTP_INTERNAL;
}
db_queue_get_count(&count);
if (count == 0)
{
player_playback_stop();
db_queue_clear(0);
}
return HTTP_NOCONTENT;
}
@ -3317,11 +3310,11 @@ jsonapi_reply_library_tracks_put(struct httpd_request *hreq)
json_object *request = NULL;
json_object *tracks;
json_object *track = NULL;
struct media_file_info *mfi = NULL;
int ret;
int err;
int32_t track_id;
int i;
int j;
request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
@ -3351,30 +3344,26 @@ jsonapi_reply_library_tracks_put(struct httpd_request *hreq)
goto error;
}
mfi = db_file_fetch_byid(track_id);
if (!mfi)
if (!db_file_id_exists(track_id))
{
DPRINTF(E_LOG, L_WEB, "Unknown track_id %d in json tracks request\n", track_id);
err = HTTP_NOTFOUND;
goto error;
}
ret = json_to_track(mfi, track);
if (ret != HTTP_OK)
for (j = 0; j < ARRAY_SIZE(track_attribs); j++)
{
err = ret;
goto error;
if (!jparse_contains_key(track, track_attribs[j].name, json_type_int))
continue;
ret = jparse_int_from_obj(track, track_attribs[j].name);
if (ret < 0)
continue;
// async, so no error check
library_item_attrib_save(track_id, track_attribs[j].type, ret);
}
ret = library_media_save(mfi);
if (ret < 0)
{
err = HTTP_INTERNAL;
goto error;
}
free_mfi(mfi, 0);
mfi = NULL;
i++;
}
@ -3386,7 +3375,6 @@ jsonapi_reply_library_tracks_put(struct httpd_request *hreq)
jparse_free(request);
if (track)
db_transaction_rollback();
free_mfi(mfi, 0);
return err;
}
@ -3397,58 +3385,41 @@ jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq)
const char *param;
uint32_t val;
int ret;
int i;
ret = safe_atoi32(hreq->path_parts[3], &track_id);
if (ret < 0)
return HTTP_INTERNAL;
param = httpd_query_value_find(hreq->query, "play_count");
if (param)
if (ret < 0 || !db_file_id_exists(track_id))
{
if (strcmp(param, "increment") == 0)
DPRINTF(E_WARN, L_WEB, "Invalid or unknown track id in request '%s'\n", hreq->path);
return HTTP_NOTFOUND;
}
for (i = 0; i < ARRAY_SIZE(track_attribs); i++)
{
param = httpd_query_value_find(hreq->query, track_attribs[i].name);
if (!param)
continue;
// Special cases
if (track_attribs[i].type == LIBRARY_ATTRIB_PLAY_COUNT && strcmp(param, "increment") == 0)
{
db_file_inc_playcount(track_id);
continue;
}
else if (strcmp(param, "reset") == 0)
if (track_attribs[i].type == LIBRARY_ATTRIB_PLAY_COUNT && strcmp(param, "reset") == 0)
{
db_file_reset_playskip_count(track_id);
}
else
{
DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count value '%s' for track '%d'.\n", param, track_id);
return HTTP_BADREQUEST;
}
}
param = httpd_query_value_find(hreq->query, "rating");
if (param)
{
ret = safe_atou32(param, &val);
if (ret < 0 || val > DB_FILES_RATING_MAX)
{
DPRINTF(E_WARN, L_WEB, "Invalid rating value '%s' for track '%d'.\n", param, track_id);
return HTTP_BADREQUEST;
continue;
}
ret = db_file_rating_update_byid(track_id, val);
if (ret < 0)
return HTTP_INTERNAL;
}
// Retreive marked tracks via "/api/search?type=tracks&expression=usermark+=+1"
param = httpd_query_value_find(hreq->query, "usermark");
if (param)
{
ret = safe_atou32(param, &val);
if (ret < 0)
{
DPRINTF(E_WARN, L_WEB, "Invalid usermark value '%s' for track '%d'.\n", param, track_id);
DPRINTF(E_WARN, L_WEB, "Invalid %s value '%s' for track '%d'.\n", track_attribs[i].name, param, track_id);
return HTTP_BADREQUEST;
}
ret = db_file_usermark_update_byid(track_id, val);
if (ret < 0)
return HTTP_INTERNAL;
library_item_attrib_save(track_id, track_attribs[i].type, val);
}
return HTTP_OK;
@ -3851,7 +3822,7 @@ jsonapi_reply_queue_save(struct httpd_request *hreq)
if (!allow_modifying_stored_playlists)
{
DPRINTF(E_LOG, L_WEB, "Modifying stored playlists is not enabled in the config file\n");
return 403;
return 403;
}
if (access(default_playlist_directory, W_OK) < 0)
@ -4457,6 +4428,68 @@ search_composers(json_object *reply, struct httpd_request *hreq, const char *par
return ret;
}
static int
search_genres(json_object *reply, struct httpd_request *hreq, const char *param_query, struct smartpl *smartpl_expression, enum media_kind media_kind)
{
json_object *type;
json_object *items;
struct query_params query_params;
int total;
int ret;
memset(&query_params, 0, sizeof(struct query_params));
ret = query_params_limit_set(&query_params, hreq);
if (ret < 0)
goto out;
type = json_object_new_object();
json_object_object_add(reply, "genres", type);
items = json_object_new_array();
json_object_object_add(type, "items", items);
query_params.type = Q_BROWSE_GENRES;
query_params.sort = S_GENRE;
ret = query_params_limit_set(&query_params, hreq);
if (ret < 0)
goto out;
if (param_query)
{
if (media_kind)
query_params.filter = db_mprintf("(f.genre LIKE '%%%q%%' AND f.media_kind = %d)", param_query, media_kind);
else
query_params.filter = db_mprintf("(f.genre LIKE '%%%q%%')", param_query);
}
else
{
query_params.filter = strdup(smartpl_expression->query_where);
query_params.having = safe_strdup(smartpl_expression->having);
query_params.order = safe_strdup(smartpl_expression->order);
if (smartpl_expression->limit > 0)
{
query_params.idx_type = I_SUB;
query_params.limit = smartpl_expression->limit;
query_params.offset = 0;
}
}
ret = fetch_browse_info(&query_params, items, &total);
if (ret < 0)
goto out;
json_object_object_add(type, "total", json_object_new_int(total));
json_object_object_add(type, "offset", json_object_new_int(query_params.offset));
json_object_object_add(type, "limit", json_object_new_int(query_params.limit));
out:
free_query_params(&query_params, 1);
return ret;
}
static int
search_playlists(json_object *reply, struct httpd_request *hreq, const char *param_query)
{
@ -4579,6 +4612,13 @@ jsonapi_reply_search(struct httpd_request *hreq)
goto error;
}
if (strstr(param_type, "genre"))
{
ret = search_genres(reply, hreq, param_query, &smartpl_expression, media_kind);
if (ret < 0)
goto error;
}
if (strstr(param_type, "playlist") && param_query)
{
ret = search_playlists(reply, hreq, param_query);
@ -4695,6 +4735,10 @@ static struct httpd_uri_map adm_handlers[] =
{ HTTPD_METHOD_GET, "^/api/search$", jsonapi_reply_search },
{ HTTPD_METHOD_GET, "^/api/listenbrainz$", jsonapi_reply_listenbrainz },
{ HTTPD_METHOD_POST, "^/api/listenbrainz/token$", jsonapi_reply_listenbrainz_token_add },
{ HTTPD_METHOD_DELETE, "^/api/listenbrainz/token$", jsonapi_reply_listenbrainz_token_delete },
{ 0, NULL, NULL }
};
@ -4706,7 +4750,7 @@ jsonapi_request(struct httpd_request *hreq)
{
int status_code;
if (!httpd_admin_check_auth(hreq))
if (!httpd_request_is_authorized(hreq))
{
return;
}
@ -4762,7 +4806,7 @@ jsonapi_init(void)
default_playlist_directory = NULL;
allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "library"), "allow_modifying_stored_playlists");
if (allow_modifying_stored_playlists)
{
{
temp_path = cfg_getstr(cfg_getsec(cfg, "library"), "default_playlist_directory");
if (temp_path)
{

View File

@ -20,21 +20,27 @@
# include <config.h>
#endif
#include <json.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/queue.h> // TAILQ_FOREACH
#include <sys/socket.h> // listen()
#include <arpa/inet.h> // inet_pton()
#include <event2/http.h>
#include <event2/http_struct.h> // flags in struct evhttp
#include <event2/keyvalq_struct.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#ifdef HAVE_LIBEVENT22
#include <event2/ws.h>
#endif
#include <pthread.h>
#include "conffile.h"
#include "misc.h"
#include "listener.h"
#include "logger.h"
#include "commands.h"
#include "httpd_internal.h"
@ -94,6 +100,294 @@ static void
closecb_worker(evutil_socket_t fd, short event, void *arg);
#ifdef HAVE_LIBEVENT22
/*
* Each session of the "notify" protocol holds this event mask
*
* The client sends the events it wants to be notified of and the event mask is
* set accordingly translating them to the LISTENER enum (see listener.h)
*/
struct ws_client
{
struct evws_connection *evws;
char name[INET6_ADDRSTRLEN];
short requested_events;
struct ws_client *next;
};
static struct ws_client *ws_clients = NULL;
/*
* Notify clients of the notify-protocol about occurred events
*
* Sends a JSON message of the form:
*
* {
* "notify": [ "update" ]
* }
*/
static char *
ws_create_notify_reply(short events, short *requested_events)
{
char *json_response;
json_object *reply;
json_object *notify;
DPRINTF(E_DBG, L_WEB, "notify callback reply: %d\n", events);
notify = json_object_new_array();
if (events & LISTENER_UPDATE)
{
json_object_array_add(notify, json_object_new_string("update"));
}
if (events & LISTENER_DATABASE)
{
json_object_array_add(notify, json_object_new_string("database"));
}
if (events & LISTENER_PAIRING)
{
json_object_array_add(notify, json_object_new_string("pairing"));
}
if (events & LISTENER_SPOTIFY)
{
json_object_array_add(notify, json_object_new_string("spotify"));
}
if (events & LISTENER_LASTFM)
{
json_object_array_add(notify, json_object_new_string("lastfm"));
}
if (events & LISTENER_SPEAKER)
{
json_object_array_add(notify, json_object_new_string("outputs"));
}
if (events & LISTENER_PLAYER)
{
json_object_array_add(notify, json_object_new_string("player"));
}
if (events & LISTENER_OPTIONS)
{
json_object_array_add(notify, json_object_new_string("options"));
}
if (events & LISTENER_VOLUME)
{
json_object_array_add(notify, json_object_new_string("volume"));
}
if (events & LISTENER_QUEUE)
{
json_object_array_add(notify, json_object_new_string("queue"));
}
reply = json_object_new_object();
json_object_object_add(reply, "notify", notify);
json_response = strdup(json_object_to_json_string(reply));
json_object_put(reply);
return json_response;
}
/* Thread: library, player, etc. (the thread the event occurred) */
static enum command_state
ws_listener_cb(void *arg, int *ret)
{
struct ws_client *client = NULL;
char *reply = NULL;
short *event_mask = arg;
for (client = ws_clients; client; client = client->next)
{
reply = ws_create_notify_reply(*event_mask, &client->requested_events);
evws_send_text(client->evws, reply);
free(reply);
}
return COMMAND_END;
}
static void
listener_cb(short event_mask, void *ctx)
{
httpd_server *server = ctx;
commands_exec_sync(server->cmdbase, ws_listener_cb, NULL, &event_mask);
}
/*
* Processes client requests to the notify-protocol
*
* Expects the message in "in" to be a JSON string of the form:
*
* {
* "notify": [ "update" ]
* }
*/
static int
ws_process_notify_request(short *requested_events, const char *in, size_t len)
{
json_tokener *tokener;
json_object *request;
json_object *item;
int count, i;
enum json_tokener_error jerr;
json_object *needle;
const char *event_type;
*requested_events = 0;
tokener = json_tokener_new();
request = json_tokener_parse_ex(tokener, in, len);
jerr = json_tokener_get_error(tokener);
if (jerr != json_tokener_success)
{
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request: %s\n", json_tokener_error_desc(jerr));
json_tokener_free(tokener);
return -1;
}
DPRINTF(E_DBG, L_WEB, "notify callback request: %s\n", json_object_to_json_string(request));
if (json_object_object_get_ex(request, "notify", &needle) && json_object_get_type(needle) == json_type_array)
{
count = json_object_array_length(needle);
for (i = 0; i < count; i++)
{
item = json_object_array_get_idx(needle, i);
if (json_object_get_type(item) == json_type_string)
{
event_type = json_object_get_string(item);
DPRINTF(E_SPAM, L_WEB, "notify callback event received: %s\n", event_type);
if (0 == strcmp(event_type, "update"))
{
*requested_events |= LISTENER_UPDATE;
}
else if (0 == strcmp(event_type, "database"))
{
*requested_events |= LISTENER_DATABASE;
}
else if (0 == strcmp(event_type, "pairing"))
{
*requested_events |= LISTENER_PAIRING;
}
else if (0 == strcmp(event_type, "spotify"))
{
*requested_events |= LISTENER_SPOTIFY;
}
else if (0 == strcmp(event_type, "lastfm"))
{
*requested_events |= LISTENER_LASTFM;
}
else if (0 == strcmp(event_type, "outputs"))
{
*requested_events |= LISTENER_SPEAKER;
}
else if (0 == strcmp(event_type, "player"))
{
*requested_events |= LISTENER_PLAYER;
}
else if (0 == strcmp(event_type, "options"))
{
*requested_events |= LISTENER_OPTIONS;
}
else if (0 == strcmp(event_type, "volume"))
{
*requested_events |= LISTENER_VOLUME;
}
else if (0 == strcmp(event_type, "queue"))
{
*requested_events |= LISTENER_QUEUE;
}
}
}
}
json_tokener_free(tokener);
json_object_put(request);
return 0;
}
static void
ws_client_msg_cb(struct evws_connection *evws, int type, const unsigned char *data, size_t len, void *arg)
{
struct ws_client *self = arg;
const char *msg = (const char *)data;
ws_process_notify_request(&self->requested_events, msg, len);
}
static void
ws_client_close_cb(struct evws_connection *evws, void *arg)
{
struct ws_client *client = NULL;
struct ws_client *prev = NULL;
for (client = ws_clients; client && client != arg; client = ws_clients->next)
{
prev = client;
}
if (client)
{
if (prev)
prev->next = client->next;
else
ws_clients = client->next;
free(client);
}
}
static void
ws_gencb(struct evhttp_request *req, void *arg)
{
struct ws_client *client;
client = calloc(1, sizeof(*client));
client->evws = evws_new_session(req, ws_client_msg_cb, client, 0);
if (!client->evws)
{
free(client);
return;
}
evws_connection_set_closecb(client->evws, ws_client_close_cb, client);
client->next = ws_clients;
ws_clients = client;
}
static int
ws_init(httpd_server *server)
{
int websocket_port = cfg_getint(cfg_getsec(cfg, "general"), "websocket_port");
if (websocket_port > 0)
{
DPRINTF(E_DBG, L_WEB,
"Libevent websocket disabled, using libwebsockets instead. Set "
"websocket_port to 0 to enable it.\n");
return 0;
}
evhttp_set_cb(server->evhttp, "/ws", ws_gencb, NULL);
listener_add(listener_cb, LISTENER_UPDATE | LISTENER_DATABASE | LISTENER_PAIRING | LISTENER_SPOTIFY | LISTENER_LASTFM
| LISTENER_SPEAKER | LISTENER_PLAYER | LISTENER_OPTIONS | LISTENER_VOLUME
| LISTENER_QUEUE, server);
return 0;
}
static void
ws_deinit(void)
{
listener_remove(listener_cb);
}
#endif
const char *
httpd_query_value_find(httpd_query *query, const char *key)
{
@ -105,7 +399,8 @@ httpd_query_iterate(httpd_query *query, httpd_query_iteratecb cb, void *arg)
{
struct evkeyval *param;
TAILQ_FOREACH(param, query, next)
// musl libc doesn't have sys/queue.h so don't use TAILQ_FOREACH
for (param = query->tqh_first; param; param = param->next.tqe_next)
{
cb(param->key, param->value, arg);
}
@ -303,9 +598,11 @@ gencb_httpd(httpd_backend *backend, void *arg)
struct httpd_request *hreq;
struct bufferevent *bufev;
#ifndef HAVE_LIBEVENT22
// Clear the proxy request flag set by evhttp if the request URI was absolute.
// It has side-effects on Connection: keep-alive
backend->flags &= ~EVHTTP_PROXY_REQUEST;
#endif
// This is a workaround for some versions of libevent (2.0 and 2.1) that don't
// detect if the client hangs up, and thus don't clean up and never call the
@ -340,7 +637,12 @@ httpd_server_free(httpd_server *server)
close(server->fd);
if (server->evhttp)
evhttp_free(server->evhttp);
{
#ifdef HAVE_LIBEVENT22
ws_deinit();
#endif
evhttp_free(server->evhttp);
}
commands_base_free(server->cmdbase);
free(server);
@ -359,7 +661,7 @@ httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_c
server->request_cb = cb;
server->request_cb_arg = arg;
server->fd = net_bind_with_reuseport(&port, SOCK_STREAM | SOCK_NONBLOCK, "httpd");
server->fd = net_bind_with_reuseport(&port, SOCK_STREAM, "httpd");
if (server->fd <= 0)
goto error;
@ -373,6 +675,9 @@ httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_c
goto error;
evhttp_set_gencb(server->evhttp, gencb_httpd, server);
#ifdef HAVE_LIBEVENT22
ws_init(server);
#endif
return server;
@ -525,6 +830,7 @@ httpd_backend_output_buffer_get(httpd_backend *backend)
int
httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data)
{
#define IPV4_MAPPED_IPV6_PREFIX "::ffff:"
httpd_connection *conn = evhttp_request_get_connection(backend);
if (!conn)
return -1;
@ -534,9 +840,57 @@ httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend
#else
evhttp_connection_get_peer(conn, (char **)addr, port);
#endif
// Just use the pure ipv4 address if it's mapped
if (strncmp(*addr, IPV4_MAPPED_IPV6_PREFIX, strlen(IPV4_MAPPED_IPV6_PREFIX)) == 0)
*addr += strlen(IPV4_MAPPED_IPV6_PREFIX);
return 0;
}
// When removing this workaround then also remove the include of arpa/inet.h
static bool
address_is_trusted_workaround(httpd_backend *backend)
{
union net_sockaddr naddr = { 0 };
const char *saddr;
uint16_t port;
DPRINTF(E_DBG, L_HTTPD, "Detected libevent version with buggy evhttp_connection_get_addr()\n");
if (httpd_backend_peer_get(&saddr, &port, backend, NULL) < 0)
return false;
if (inet_pton(AF_INET, saddr, &naddr.sin.sin_addr) == 1)
naddr.sa.sa_family = AF_INET;
else if (inet_pton(AF_INET6, saddr, &naddr.sin6.sin6_addr) == 1)
naddr.sa.sa_family = AF_INET6;
else
return false;
return net_peer_address_is_trusted(&naddr);
}
bool
httpd_backend_peer_is_trusted(httpd_backend *backend)
{
const struct sockaddr *addr;
httpd_connection *conn = evhttp_request_get_connection(backend);
if (!conn)
return false;
addr = evhttp_connection_get_addr(conn);
if (!addr)
return false;
// Workaround for bug in libevent 2.1.6 and .8, see #1775
if (addr->sa_family == AF_UNSPEC)
return address_is_trusted_workaround(backend);
return net_peer_address_is_trusted((union net_sockaddr *)addr);
}
int
httpd_backend_method_get(enum httpd_methods *method, httpd_backend *backend)
{

View File

@ -43,15 +43,10 @@
static int
oauth_reply_spotify(struct httpd_request *hreq)
{
char redirect_uri[256];
const char *errmsg;
int httpd_port;
int ret;
httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
snprintf(redirect_uri, sizeof(redirect_uri), "http://owntone.local:%d/oauth/spotify", httpd_port);
ret = spotifywebapi_oauth_callback(hreq->query, redirect_uri, &errmsg);
ret = spotifywebapi_oauth_callback(hreq->query, &errmsg);
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Could not parse Spotify OAuth callback '%s': %s\n", hreq->uri, errmsg);

View File

@ -30,18 +30,17 @@
#include <sys/types.h>
#include <limits.h>
#include "mxml-compat.h"
#include "httpd_internal.h"
#include "logger.h"
#include "db.h"
#include "conffile.h"
#include "misc.h"
#include "misc_xml.h"
#include "transcode.h"
#include "parsers/rsp_parser.h"
#define RSP_VERSION "1.0"
#define RSP_XML_ROOT "?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?"
#define RSP_XML_DECLARATION "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>"
#define F_FULL (1 << 0)
#define F_BROWSE (1 << 1)
@ -120,12 +119,12 @@ static const struct field_map rsp_fields[] =
/* -------------------------------- HELPERS --------------------------------- */
static int
mxml_to_evbuf(struct evbuffer *evbuf, mxml_node_t *tree)
xml_to_evbuf(struct evbuffer *evbuf, xml_node *tree)
{
char *xml;
int ret;
xml = mxmlSaveAllocString(tree, MXML_NO_CALLBACK);
xml = xml_to_string(tree, RSP_XML_DECLARATION);
if (!xml)
{
DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n");
@ -143,48 +142,53 @@ mxml_to_evbuf(struct evbuffer *evbuf, mxml_node_t *tree)
return 0;
}
static int
rsp_xml_response_new(xml_node **response_ptr, int errorcode, const char *errorstring, int records, int totalrecords)
{
xml_node *node;
xml_node *response = xml_new_node(NULL, "response", NULL);
xml_node *status = xml_new_node(response, "status", NULL);
if (!response || !status)
return -1;
xml_new_node_textf(status, "errorcode", "%d", errorcode);
node = xml_new_node(status, "errorstring", errorstring);
if (errorstring && *errorstring == '\0')
xml_new_text(node, ""); // Prevents sending <errorstring/> which the Soundbridge may not understand
xml_new_node_textf(status, "records", "%d", records);
xml_new_node_textf(status, "totalrecords", "%d", totalrecords);
if (response_ptr)
*response_ptr = response;
return 0;
}
static void
rsp_send_error(struct httpd_request *hreq, char *errmsg)
{
mxml_node_t *reply;
mxml_node_t *status;
mxml_node_t *node;
xml_node *response = NULL;
int ret;
/* We'd use mxmlNewXML(), but then we can't put any attributes
* on the root node and we need some.
*/
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
node = mxmlNewElement(reply, "response");
status = mxmlNewElement(node, "status");
/* Status block */
node = mxmlNewElement(status, "errorcode");
mxmlNewText(node, 0, "1");
node = mxmlNewElement(status, "errorstring");
mxmlNewText(node, 0, errmsg);
node = mxmlNewElement(status, "records");
mxmlNewText(node, 0, "0");
node = mxmlNewElement(status, "totalrecords");
mxmlNewText(node, 0, "0");
ret = mxml_to_evbuf(hreq->out_body, reply);
mxmlDelete(reply);
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 1, errmsg, 0, 0));
ret = xml_to_evbuf(hreq->out_body, response);
if (ret < 0)
{
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
return;
}
goto error;
httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
httpd_header_add(hreq->out_headers, "Connection", "close");
httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
xml_free(response);
return;
error:
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
xml_free(response);
}
static int
@ -259,12 +263,12 @@ query_params_set(struct query_params *qp, struct httpd_request *hreq)
}
static void
rsp_send_reply(struct httpd_request *hreq, mxml_node_t *reply)
rsp_send_reply(struct httpd_request *hreq, xml_node *reply)
{
int ret;
ret = mxml_to_evbuf(hreq->out_body, reply);
mxmlDelete(reply);
ret = xml_to_evbuf(hreq->out_body, reply);
xml_free(reply);
if (ret < 0)
{
@ -284,7 +288,7 @@ rsp_request_authorize(struct httpd_request *hreq)
char *passwd;
int ret;
if (net_peer_address_is_trusted(hreq->peer_address))
if (httpd_request_is_trusted(hreq))
return 0;
passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password");
@ -310,10 +314,8 @@ rsp_request_authorize(struct httpd_request *hreq)
static int
rsp_reply_info(struct httpd_request *hreq)
{
mxml_node_t *reply;
mxml_node_t *status;
mxml_node_t *info;
mxml_node_t *node;
xml_node *response;
xml_node *info;
cfg_t *lib;
char *library;
uint32_t songcount;
@ -323,43 +325,16 @@ rsp_reply_info(struct httpd_request *hreq)
lib = cfg_getsec(cfg, "library");
library = cfg_getstr(lib, "name");
/* We'd use mxmlNewXML(), but then we can't put any attributes
* on the root node and we need some.
*/
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", 0, 0));
node = mxmlNewElement(reply, "response");
status = mxmlNewElement(node, "status");
info = mxmlNewElement(node, "info");
info = xml_new_node(response, "info", NULL);
/* Status block */
node = mxmlNewElement(status, "errorcode");
mxmlNewText(node, 0, "0");
node = mxmlNewElement(status, "errorstring");
mxmlNewText(node, 0, "");
node = mxmlNewElement(status, "records");
mxmlNewText(node, 0, "0");
node = mxmlNewElement(status, "totalrecords");
mxmlNewText(node, 0, "0");
/* Info block */
node = mxmlNewElement(info, "count");
mxmlNewTextf(node, 0, "%d", (int)songcount);
node = mxmlNewElement(info, "rsp-version");
mxmlNewText(node, 0, RSP_VERSION);
node = mxmlNewElement(info, "server-version");
mxmlNewText(node, 0, VERSION);
node = mxmlNewElement(info, "name");
mxmlNewText(node, 0, library);
rsp_send_reply(hreq, reply);
xml_new_node_textf(info, "count", "%d", (int)songcount);
xml_new_node(info, "rsp-version", RSP_VERSION);
xml_new_node(info, "server-version", VERSION);
xml_new_node(info, "name", library);
rsp_send_reply(hreq, response);
return 0;
}
@ -369,11 +344,9 @@ rsp_reply_db(struct httpd_request *hreq)
struct query_params qp;
struct db_playlist_info dbpli;
char **strval;
mxml_node_t *reply;
mxml_node_t *status;
mxml_node_t *pls;
mxml_node_t *pl;
mxml_node_t *node;
xml_node *response;
xml_node *pls;
xml_node *pl;
int i;
int ret;
@ -391,27 +364,9 @@ rsp_reply_db(struct httpd_request *hreq)
return -1;
}
/* We'd use mxmlNewXML(), but then we can't put any attributes
* on the root node and we need some.
*/
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", qp.results, qp.results));
node = mxmlNewElement(reply, "response");
status = mxmlNewElement(node, "status");
pls = mxmlNewElement(node, "playlists");
/* Status block */
node = mxmlNewElement(status, "errorcode");
mxmlNewText(node, 0, "0");
node = mxmlNewElement(status, "errorstring");
mxmlNewText(node, 0, "");
node = mxmlNewElement(status, "records");
mxmlNewTextf(node, 0, "%d", qp.results);
node = mxmlNewElement(status, "totalrecords");
mxmlNewTextf(node, 0, "%d", qp.results);
pls = xml_new_node(response, "playlists", NULL);
/* Playlists block (all playlists) */
while (((ret = db_query_fetch_pl(&dbpli, &qp)) == 0) && (dbpli.id))
@ -421,7 +376,7 @@ rsp_reply_db(struct httpd_request *hreq)
continue;
/* Playlist block (one playlist) */
pl = mxmlNewElement(pls, "playlist");
pl = xml_new_node(pls, "playlist", NULL);
for (i = 0; pl_fields[i].field; i++)
{
@ -429,8 +384,7 @@ rsp_reply_db(struct httpd_request *hreq)
{
strval = (char **) ((char *)&dbpli + pl_fields[i].offset);
node = mxmlNewElement(pl, pl_fields[i].field);
mxmlNewText(node, 0, *strval);
xml_new_node(pl, pl_fields[i].field, *strval);
}
}
}
@ -439,7 +393,7 @@ rsp_reply_db(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
mxmlDelete(reply);
xml_free(response);
db_query_end(&qp);
rsp_send_error(hreq, "Error fetching query results");
return -1;
@ -447,15 +401,83 @@ rsp_reply_db(struct httpd_request *hreq)
/* HACK
* Add a dummy empty string to the playlists element if there is no data
* to return - this prevents mxml from sending out an empty <playlists/>
* to return - this prevents us from sending out an empty <playlists/>
* tag that the SoundBridge does not handle. It's hackish, but it works.
*/
if (qp.results == 0)
mxmlNewText(pls, 0, "");
xml_new_text(pls, "");
db_query_end(&qp);
rsp_send_reply(hreq, reply);
rsp_send_reply(hreq, response);
return 0;
}
static int
item_add(xml_node *parent, struct query_params *qp, enum transcode_profile spk_profile, const char *user_agent, const char *accept_codecs, int mode)
{
struct media_quality quality = { 0 };
struct db_media_file_info dbmfi;
struct transcode_metadata_string xcode_metadata;
enum transcode_profile profile;
const char *orgcodec = NULL;
uint32_t len_ms;
xml_node *item;
char **strval;
int ret;
int i;
ret = db_query_fetch_file(&dbmfi, qp);
if (ret != 0)
return ret;
profile = transcode_needed(user_agent, accept_codecs, dbmfi.codectype);
if (profile == XCODE_UNKNOWN)
{
DPRINTF(E_LOG, L_DAAP, "Cannot transcode '%s', codec type is unknown\n", dbmfi.fname);
}
else if (profile != XCODE_NONE)
{
if (spk_profile != XCODE_NONE)
profile = spk_profile; // User has configured a specific transcode format for this speaker
orgcodec = dbmfi.codectype;
if (safe_atou32(dbmfi.song_length, &len_ms) < 0)
len_ms = 3 * 60 * 1000; // just a fallback default
safe_atoi32(dbmfi.samplerate, &quality.sample_rate);
safe_atoi32(dbmfi.bits_per_sample, &quality.bits_per_sample);
safe_atoi32(dbmfi.channels, &quality.channels);
quality.bit_rate = cfg_getint(cfg_getsec(cfg, "streaming"), "bit_rate");
transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms);
dbmfi.type = xcode_metadata.type;
dbmfi.codectype = xcode_metadata.codectype;
dbmfi.description = xcode_metadata.description;
dbmfi.file_size = xcode_metadata.file_size;
dbmfi.bitrate = xcode_metadata.bitrate;
}
// Now add block with content
item = xml_new_node(parent, "item", NULL);
for (i = 0; rsp_fields[i].field; i++)
{
if (!(rsp_fields[i].flags & mode))
continue;
strval = (char **) ((char *)&dbmfi + rsp_fields[i].offset);
if (!(*strval) || (strlen(*strval) == 0))
continue;
xml_new_node(item, rsp_fields[i].field, *strval);
// In case we are transcoding
if (rsp_fields[i].offset == dbmfi_offsetof(codectype) && orgcodec)
xml_new_node(item, "original_codec", orgcodec);
}
return 0;
}
@ -464,25 +486,20 @@ static int
rsp_reply_playlist(struct httpd_request *hreq)
{
struct query_params qp;
struct db_media_file_info dbmfi;
const char *param;
const char *ua;
const char *client_codecs;
char **strval;
mxml_node_t *reply;
mxml_node_t *status;
mxml_node_t *items;
mxml_node_t *item;
mxml_node_t *node;
const char *accept_codecs;
enum transcode_profile spk_profile;
xml_node *response;
xml_node *items;
int mode;
int records;
int transcode;
int32_t bitrate;
int i;
int ret;
memset(&qp, 0, sizeof(struct query_params));
accept_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
spk_profile = httpd_xcode_profile_get(hreq);
ret = safe_atoi32(hreq->path_parts[2], &qp.id);
if (ret < 0)
{
@ -537,99 +554,23 @@ rsp_reply_playlist(struct httpd_request *hreq)
if (qp.limit && (records > qp.limit))
records = qp.limit;
/* We'd use mxmlNewXML(), but then we can't put any attributes
* on the root node and we need some.
*/
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", records, qp.results));
node = mxmlNewElement(reply, "response");
status = mxmlNewElement(node, "status");
items = mxmlNewElement(node, "items");
/* Status block */
node = mxmlNewElement(status, "errorcode");
mxmlNewText(node, 0, "0");
node = mxmlNewElement(status, "errorstring");
mxmlNewText(node, 0, "");
node = mxmlNewElement(status, "records");
mxmlNewTextf(node, 0, "%d", records);
node = mxmlNewElement(status, "totalrecords");
mxmlNewTextf(node, 0, "%d", qp.results);
/* Items block (all items) */
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
// Add a parent items block (all items), and then one item per file
items = xml_new_node(response, "items", NULL);
do
{
ua = httpd_header_find(hreq->in_headers, "User-Agent");
client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
transcode = transcode_needed(ua, client_codecs, dbmfi.codectype);
/* Item block (one item) */
item = mxmlNewElement(items, "item");
for (i = 0; rsp_fields[i].field; i++)
{
if (!(rsp_fields[i].flags & mode))
continue;
strval = (char **) ((char *)&dbmfi + rsp_fields[i].offset);
if (!(*strval) || (strlen(*strval) == 0))
continue;
node = mxmlNewElement(item, rsp_fields[i].field);
if (!transcode)
mxmlNewText(node, 0, *strval);
else
{
switch (rsp_fields[i].offset)
{
case dbmfi_offsetof(type):
mxmlNewText(node, 0, "wav");
break;
case dbmfi_offsetof(bitrate):
bitrate = 0;
ret = safe_atoi32(dbmfi.samplerate, &bitrate);
if ((ret < 0) || (bitrate == 0))
bitrate = 1411;
else
bitrate = (bitrate * 8) / 250;
mxmlNewTextf(node, 0, "%d", bitrate);
break;
case dbmfi_offsetof(description):
mxmlNewText(node, 0, "wav audio file");
break;
case dbmfi_offsetof(codectype):
mxmlNewText(node, 0, "wav");
node = mxmlNewElement(item, "original_codec");
mxmlNewText(node, 0, *strval);
break;
default:
mxmlNewText(node, 0, *strval);
break;
}
}
}
ret = item_add(items, &qp, spk_profile, hreq->user_agent, accept_codecs, mode);
}
while (ret == 0);
if (qp.filter)
free(qp.filter);
free(qp.filter);
if (ret < 0)
{
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
mxmlDelete(reply);
xml_free(response);
db_query_end(&qp);
rsp_send_error(hreq, "Error fetching query results");
return -1;
@ -637,15 +578,15 @@ rsp_reply_playlist(struct httpd_request *hreq)
/* HACK
* Add a dummy empty string to the items element if there is no data
* to return - this prevents mxml from sending out an empty <items/>
* to return - this prevents us from sending out an empty <items/>
* tag that the SoundBridge does not handle. It's hackish, but it works.
*/
if (qp.results == 0)
mxmlNewText(items, 0, "");
xml_new_text(items, "");
db_query_end(&qp);
rsp_send_reply(hreq, reply);
rsp_send_reply(hreq, response);
return 0;
}
@ -655,10 +596,8 @@ rsp_reply_browse(struct httpd_request *hreq)
{
struct query_params qp;
char *browse_item;
mxml_node_t *reply;
mxml_node_t *status;
mxml_node_t *items;
mxml_node_t *node;
xml_node *response;
xml_node *items;
int records;
int ret;
@ -719,33 +658,14 @@ rsp_reply_browse(struct httpd_request *hreq)
if (qp.limit && (records > qp.limit))
records = qp.limit;
/* We'd use mxmlNewXML(), but then we can't put any attributes
* on the root node and we need some.
*/
reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", records, qp.results));
node = mxmlNewElement(reply, "response");
status = mxmlNewElement(node, "status");
items = mxmlNewElement(node, "items");
/* Status block */
node = mxmlNewElement(status, "errorcode");
mxmlNewText(node, 0, "0");
node = mxmlNewElement(status, "errorstring");
mxmlNewText(node, 0, "");
node = mxmlNewElement(status, "records");
mxmlNewTextf(node, 0, "%d", records);
node = mxmlNewElement(status, "totalrecords");
mxmlNewTextf(node, 0, "%d", qp.results);
items = xml_new_node(response, "items", NULL);
/* Items block (all items) */
while (((ret = db_query_fetch_string(&browse_item, &qp)) == 0) && (browse_item))
{
node = mxmlNewElement(items, "item");
mxmlNewText(node, 0, browse_item);
xml_new_node(items, "item", browse_item);
}
if (qp.filter)
@ -755,7 +675,7 @@ rsp_reply_browse(struct httpd_request *hreq)
{
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
mxmlDelete(reply);
xml_free(response);
db_query_end(&qp);
rsp_send_error(hreq, "Error fetching query results");
return -1;
@ -763,15 +683,15 @@ rsp_reply_browse(struct httpd_request *hreq)
/* HACK
* Add a dummy empty string to the items element if there is no data
* to return - this prevents mxml from sending out an empty <items/>
* to return - this prevents us from sending out an empty <items/>
* tag that the SoundBridge does not handle. It's hackish, but it works.
*/
if (qp.results == 0)
mxmlNewText(items, 0, "");
xml_new_text(items, "");
db_query_end(&qp);
rsp_send_reply(hreq, reply);
rsp_send_reply(hreq, response);
return 0;
}
@ -805,6 +725,16 @@ rsp_stream(struct httpd_request *hreq)
// /rsp/stream/36364
// /rsp/db/0?query=id%3D36365&type=full
// /rsp/stream/36365
//
// Headers sent from Roku M2000 and M1001 in stream requests (and other?):
//
// 'User-Agent': 'Roku SoundBridge/3.0'
// 'Host': '192.168.1.119:3689'
// 'Accept': '*/*'
// 'Pragma': 'no-cache'
// 'accept-codecs': 'wma,mpeg,wav,mp4a,alac'
// 'rsp-version': '0.1'
// 'transcode-codecs': 'wav,mp3'
static struct httpd_uri_map rsp_handlers[] =
{
{

View File

@ -228,7 +228,7 @@ session_free(struct streaming_session *session)
}
static struct streaming_session *
session_new(struct httpd_request *hreq, bool icy_is_requested, enum player_format format, struct media_quality quality)
session_new(struct httpd_request *hreq, bool icy_is_requested, enum media_format format, struct media_quality quality)
{
struct streaming_session *session;
int audio_fd;
@ -279,7 +279,7 @@ streaming_mp3_handler(struct httpd_request *hreq)
httpd_header_add(hreq->out_headers, "icy-metaint", buf);
}
session = session_new(hreq, icy_is_requested, PLAYER_FORMAT_MP3, streaming_default_quality);
session = session_new(hreq, icy_is_requested, MEDIA_FORMAT_MP3, streaming_default_quality);
if (!session)
return -1; // Error sent by caller

View File

@ -36,9 +36,11 @@
static int
setup(struct input_source *source)
{
struct transcode_decode_setup_args decode_args = { .profile = XCODE_PCM_NATIVE, .path = source->path, .len_ms = source->len_ms };
struct transcode_encode_setup_args encode_args = { .profile = XCODE_PCM_NATIVE, };
struct transcode_ctx *ctx;
ctx = transcode_setup(XCODE_PCM_NATIVE, NULL, source->data_kind, source->path, source->len_ms, NULL);
ctx = transcode_setup(decode_args, encode_args);
if (!ctx)
return -1;

View File

@ -173,8 +173,9 @@ streamurl_process(struct input_metadata *metadata, const char *url)
{
struct http_client_ctx client = { 0 };
struct keyval kv = { 0 };
struct evbuffer *evbuf;
struct evbuffer *evbuf = NULL;
const char *content_type;
const char *artwork_url;
char *body;
int ret;
@ -186,6 +187,21 @@ streamurl_process(struct input_metadata *metadata, const char *url)
return -1;
}
// If the StreamUrl contains a keyword followed by the actual url, e.g. http://metadata.cdnstream1.com/?yadayada&ALBUM_ART=https%3A%2F%2Fis1-ssl.mzstatic.com%2Fimage%2Fthumb%2FMusic%2F11%2Fcc%2F21%2Fmzi.nepwiuir.jpg
if (streamurl_map[0].words)
{
ret = http_form_urldecode(&kv, url);
if (ret < 0)
return -1;
artwork_url = keyval_get(&kv, streamurl_map[0].words);
metadata->artwork_url = safe_strdup(artwork_url);
keyval_clear(&kv);
if (metadata->artwork_url)
goto out;
}
DPRINTF(E_DBG, L_PLAYER, "Downloading StreamUrl resource '%s'\n", url);
CHECK_NULL(L_PLAYER, evbuf = evbuffer_new());
@ -219,7 +235,8 @@ streamurl_process(struct input_metadata *metadata, const char *url)
out:
keyval_clear(&kv);
evbuffer_free(evbuf);
if (evbuf)
evbuffer_free(evbuf);
streamurl_settings_unload();
return ret;
}
@ -276,11 +293,14 @@ metadata_prepare(struct input_source *source)
// Note we map title to album, because clients should show stream name as title
swap_pointers(&prepared_metadata.parsed.album, &m->title);
// In this case we have to go async to download the url and process the content
if (m->url && !artwork_extension_is_artwork(m->url))
worker_execute(streamurl_cb, m->url, strlen(m->url) + 1, 0);
else
swap_pointers(&prepared_metadata.parsed.artwork_url, &m->url);
if (! SETTINGS_GETBOOL("artwork", "streamurl_ignore"))
{
// In this case we have to go async to download the url and process the content
if (m->url && !artwork_extension_is_artwork(m->url))
worker_execute(streamurl_cb, m->url, strlen(m->url) + 1, 0);
else
swap_pointers(&prepared_metadata.parsed.artwork_url, &m->url);
}
http_icy_metadata_free(m, 0);
return 0;
@ -295,6 +315,8 @@ metadata_prepare(struct input_source *source)
static int
setup(struct input_source *source)
{
struct transcode_decode_setup_args decode_args = { .profile = XCODE_PCM_NATIVE, .is_http = true, .len_ms = source->len_ms };
struct transcode_encode_setup_args encode_args = { .profile = XCODE_PCM_NATIVE, };
struct transcode_ctx *ctx;
char *url;
@ -303,8 +325,9 @@ setup(struct input_source *source)
free(source->path);
source->path = url;
decode_args.path = url;
ctx = transcode_setup(XCODE_PCM_NATIVE, NULL, source->data_kind, source->path, source->len_ms, NULL);
ctx = transcode_setup(decode_args, encode_args);
if (!ctx)
return -1;

View File

@ -11,16 +11,36 @@ PROTO_SRC = \
src/proto/mercury.pb-c.c src/proto/mercury.pb-c.h \
src/proto/metadata.pb-c.c src/proto/metadata.pb-c.h
HTTP_PROTO_SRC = \
src/proto/connectivity.pb-c.c src/proto/connectivity.pb-c.h \
src/proto/clienttoken.pb-c.c src/proto/clienttoken.pb-c.h \
src/proto/login5_user_info.pb-c.h src/proto/login5_user_info.pb-c.c \
src/proto/login5.pb-c.h src/proto/login5.pb-c.c \
src/proto/login5_identifiers.pb-c.h src/proto/login5_identifiers.pb-c.c \
src/proto/login5_credentials.pb-c.h src/proto/login5_credentials.pb-c.c \
src/proto/login5_client_info.pb-c.h src/proto/login5_client_info.pb-c.c \
src/proto/login5_challenges_hashcash.pb-c.h src/proto/login5_challenges_hashcash.pb-c.c \
src/proto/login5_challenges_code.pb-c.h src/proto/login5_challenges_code.pb-c.c \
src/proto/google_duration.pb-c.h src/proto/google_duration.pb-c.c \
src/proto/storage_resolve.pb-c.h src/proto/storage_resolve.pb-c.c \
src/proto/extended_metadata.pb-c.h src/proto/extended_metadata.pb-c.c \
src/proto/extension_kind.pb-c.h src/proto/extension_kind.pb-c.c \
src/proto/entity_extension_data.pb-c.h src/proto/entity_extension_data.pb-c.c \
src/proto/google_any.pb-c.h src/proto/google_any.pb-c.c
CORE_SRC = \
src/librespot-c.c src/connection.c src/channel.c src/crypto.c src/commands.c
src/librespot-c.c src/connection.c src/channel.c src/crypto.c src/commands.c \
src/http.c
librespot_c_a_SOURCES = \
$(CORE_SRC) \
$(SHANNON_SRC) \
$(PROTO_SRC)
$(PROTO_SRC) \
$(HTTP_PROTO_SRC)
noinst_HEADERS = \
librespot-c.h src/librespot-c-internal.h src/connection.h \
src/channel.h src/crypto.h src/commands.h
src/channel.h src/crypto.h src/commands.h src/http.h
EXTRA_DIST = README.md LICENSE

View File

@ -1,10 +1,18 @@
AC_INIT([librespot-c], [0.1])
AC_INIT([librespot-c], [0.6])
AC_CONFIG_AUX_DIR([.])
AM_INIT_AUTOMAKE([foreign subdir-objects])
AM_SILENT_RULES([yes])
dnl Defines _GNU_SOURCE globally when needed
AC_USE_SYSTEM_EXTENSIONS
AC_PROG_CC
AM_PROG_AR
AC_PROG_RANLIB
AM_CPPFLAGS="-Wall"
AC_SUBST([AM_CPPFLAGS])
AC_CHECK_HEADERS_ONCE([sys/utsname.h])
AC_CHECK_HEADERS([endian.h sys/endian.h libkern/OSByteOrder.h], [found_endian_headers=yes; break;])
@ -17,6 +25,7 @@ PKG_CHECK_MODULES([JSON_C], [json-c])
PKG_CHECK_MODULES([LIBGCRYPT], [libgcrypt], [], [
AM_PATH_LIBGCRYPT([], [], [AC_MSG_ERROR([[libgcrypt is required]])])
])
PKG_CHECK_MODULES([LIBCURL], [libcurl])
PKG_CHECK_MODULES([LIBPROTOBUF_C], [libprotobuf-c])

Some files were not shown because too many files have changed in this diff Show More