├── .clang-format ├── .dev ├── Makefile ├── devcontainer │ ├── .scripts │ │ ├── init-devcontainer-cli.sh │ │ └── install-devcontainer-tools.sh │ ├── data │ │ └── devcontainer-owntone.conf │ ├── devcontainer.env │ └── ubuntu │ │ ├── Dockerfile │ │ └── devcontainer.json └── vscode │ ├── c_cpp_properties.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── other-issue.md ├── codeql │ └── codeql-config.yml └── workflows │ ├── build_htdocs.yml │ ├── codeql-analysis.yml │ ├── freebsd.yml │ ├── gh-pages.yml │ ├── macos.yml │ ├── macos_12.yml │ ├── ubuntu.yml │ └── webui_lint.yml ├── .gitignore ├── .travis.yml ├── AUTHORS ├── COPYING ├── ChangeLog ├── INSTALL.md ├── Makefile.am ├── NEWS ├── README.md ├── UPGRADING ├── _config.yml ├── build-aux └── config.rpath ├── configure.ac ├── docs ├── advanced │ ├── multiple-instances.md │ ├── outputs-alsa.md │ ├── outputs-pulse.md │ ├── radio-streams.md │ └── remote-access.md ├── artwork.md ├── assets │ ├── extra.css │ ├── extra.js │ ├── favicon.ico │ ├── images │ │ ├── screenshot-audiobooks-authors.png │ │ ├── screenshot-audiobooks-books.png │ │ ├── screenshot-files.png │ │ ├── screenshot-menu.png │ │ ├── screenshot-music-album.png │ │ ├── screenshot-music-albums-options.png │ │ ├── screenshot-music-albums.png │ │ ├── screenshot-music-artist.png │ │ ├── screenshot-music-artists.png │ │ ├── screenshot-music-browse.png │ │ ├── screenshot-music-spotify.png │ │ ├── screenshot-now-playing.png │ │ ├── screenshot-outputs.png │ │ ├── screenshot-podcast.png │ │ ├── screenshot-podcasts.png │ │ ├── screenshot-queue.png │ │ └── screenshot-search.png │ └── logo.svg ├── audio-outputs │ ├── airplay.md │ ├── chromecast.md │ ├── local-audio.md │ ├── mobile.md │ ├── roku.md │ ├── streaming.md │ └── web.md ├── building.md ├── changelog.md ├── configuration.md ├── control-clients │ ├── cli-api.md │ ├── desktop.md │ ├── mobile.md │ └── web.md ├── development.md ├── getting-started.md ├── index.md ├── installation.md ├── integrations │ ├── lastfm.md │ └── spotify.md ├── json-api.md ├── library.md ├── media-clients.md ├── playlists.md └── smart-playlists.md ├── htdocs ├── Makefile.am ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── assets │ ├── index.css │ └── index.js ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── logo.svg ├── mstile-150x150.png ├── safari-pinned-tab.svg └── site.webmanifest ├── m4 ├── .gitignore ├── ax_prog_bison.m4 ├── ax_prog_flex.m4 └── owntone_checks.m4 ├── mkdocs.sh ├── mkdocs.yml ├── owntone.8 ├── owntone.conf.in ├── owntone.service.in ├── owntone.spec.in ├── owntone@.service.in ├── scripts ├── freebsd_install.sh ├── freebsd_start.sh └── pairinghelper.sh ├── sqlext ├── Makefile.am └── sqlext.c ├── src ├── .gitignore ├── Makefile.am ├── artwork.c ├── artwork.h ├── cache.c ├── cache.h ├── commands.c ├── commands.h ├── conffile.c ├── conffile.h ├── daap_query.gperf ├── dacp_prop.gperf ├── db.c ├── db.h ├── db_init.c ├── db_init.h ├── db_upgrade.c ├── db_upgrade.h ├── dmap_common.c ├── dmap_common.h ├── dmap_fields.gperf ├── evrtsp │ ├── evrtsp.h │ ├── log.h │ ├── rtsp-internal.h │ └── rtsp.c ├── evthr.c ├── evthr.h ├── http.c ├── http.h ├── httpd.c ├── httpd.h ├── httpd_artworkapi.c ├── httpd_daap.c ├── httpd_daap.h ├── httpd_dacp.c ├── httpd_internal.h ├── httpd_jsonapi.c ├── httpd_libevhttp.c ├── httpd_oauth.c ├── httpd_rsp.c ├── httpd_streaming.c ├── input.c ├── input.h ├── inputs │ ├── file.c │ ├── http.c │ ├── librespot-c │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── Makefile.am │ │ ├── README.md │ │ ├── configure.ac │ │ ├── librespot-c.h │ │ ├── src │ │ │ ├── channel.c │ │ │ ├── channel.h │ │ │ ├── commands.c │ │ │ ├── commands.h │ │ │ ├── connection.c │ │ │ ├── connection.h │ │ │ ├── crypto.c │ │ │ ├── crypto.h │ │ │ ├── http.c │ │ │ ├── http.h │ │ │ ├── librespot-c-internal.h │ │ │ ├── librespot-c.c │ │ │ ├── proto │ │ │ │ ├── authentication.pb-c.c │ │ │ │ ├── authentication.pb-c.h │ │ │ │ ├── authentication.proto │ │ │ │ ├── clienttoken.pb-c.c │ │ │ │ ├── clienttoken.pb-c.h │ │ │ │ ├── clienttoken.proto │ │ │ │ ├── connectivity.pb-c.c │ │ │ │ ├── connectivity.pb-c.h │ │ │ │ ├── connectivity.proto │ │ │ │ ├── google_duration.pb-c.c │ │ │ │ ├── google_duration.pb-c.h │ │ │ │ ├── google_duration.proto │ │ │ │ ├── keyexchange.pb-c.c │ │ │ │ ├── keyexchange.pb-c.h │ │ │ │ ├── keyexchange.proto │ │ │ │ ├── login5.pb-c.c │ │ │ │ ├── login5.pb-c.h │ │ │ │ ├── login5.proto │ │ │ │ ├── login5_challenges_code.pb-c.c │ │ │ │ ├── login5_challenges_code.pb-c.h │ │ │ │ ├── login5_challenges_code.proto │ │ │ │ ├── login5_challenges_hashcash.pb-c.c │ │ │ │ ├── login5_challenges_hashcash.pb-c.h │ │ │ │ ├── login5_challenges_hashcash.proto │ │ │ │ ├── login5_client_info.pb-c.c │ │ │ │ ├── login5_client_info.pb-c.h │ │ │ │ ├── login5_client_info.proto │ │ │ │ ├── login5_credentials.pb-c.c │ │ │ │ ├── login5_credentials.pb-c.h │ │ │ │ ├── login5_credentials.proto │ │ │ │ ├── login5_identifiers.pb-c.c │ │ │ │ ├── login5_identifiers.pb-c.h │ │ │ │ ├── login5_identifiers.proto │ │ │ │ ├── login5_user_info.pb-c.c │ │ │ │ ├── login5_user_info.pb-c.h │ │ │ │ ├── login5_user_info.proto │ │ │ │ ├── mercury.pb-c.c │ │ │ │ ├── mercury.pb-c.h │ │ │ │ ├── mercury.proto │ │ │ │ ├── metadata.pb-c.c │ │ │ │ ├── metadata.pb-c.h │ │ │ │ ├── metadata.proto │ │ │ │ ├── spirc.proto │ │ │ │ ├── storage_resolve.pb-c.c │ │ │ │ ├── storage_resolve.pb-c.h │ │ │ │ └── storage_resolve.proto │ │ │ └── shannon │ │ │ │ ├── Shannon.h │ │ │ │ ├── ShannonFast.c │ │ │ │ └── ShannonInternal.h │ │ └── tests │ │ │ ├── .gitignore │ │ │ ├── Makefile.am │ │ │ ├── test1.c │ │ │ └── test2.c │ ├── pipe.c │ ├── spotify.c │ ├── spotify.h │ ├── spotify_librespotc.c │ └── timer.c ├── lastfm.c ├── lastfm.h ├── library.c ├── library.h ├── library │ ├── filescanner.c │ ├── filescanner.h │ ├── filescanner_ffmpeg.c │ ├── filescanner_itunes.c │ ├── filescanner_playlist.c │ ├── filescanner_smartpl.c │ ├── rssscanner.c │ ├── spotify_webapi.c │ └── spotify_webapi.h ├── listenbrainz.c ├── listenbrainz.h ├── listener.c ├── listener.h ├── logger.c ├── logger.h ├── main.c ├── mdns.h ├── mdns_avahi.c ├── mdns_dnssd.c ├── misc.c ├── misc.h ├── misc_json.c ├── misc_json.h ├── misc_xml.c ├── misc_xml.h ├── mpd.c ├── mpd.h ├── outputs.c ├── outputs.h ├── outputs │ ├── airplay.c │ ├── airplay_events.c │ ├── airplay_events.h │ ├── alsa.c │ ├── cast.c │ ├── cast_channel.pb-c.c │ ├── cast_channel.pb-c.h │ ├── cast_channel.v0.pb-c.c │ ├── cast_channel.v0.pb-c.h │ ├── dummy.c │ ├── fifo.c │ ├── plist_wrap.h │ ├── pulse.c │ ├── raop.c │ ├── rcp.c │ ├── rtp_common.c │ ├── rtp_common.h │ └── streaming.c ├── pair_ap │ ├── pair-internal.h │ ├── pair-tlv.c │ ├── pair-tlv.h │ ├── pair.c │ ├── pair.h │ ├── pair_fruit.c │ └── pair_homekit.c ├── parsers │ ├── .gitignore │ ├── daap_lexer.l │ ├── daap_parser.y │ ├── mpd_lexer.l │ ├── mpd_parser.y │ ├── rsp_lexer.l │ ├── rsp_parser.y │ ├── smartpl_lexer.l │ └── smartpl_parser.y ├── player.c ├── player.h ├── remote_pairing.c ├── remote_pairing.h ├── rng.c ├── rng.h ├── settings.c ├── settings.h ├── smartpl_query.c ├── smartpl_query.h ├── transcode.c ├── transcode.h ├── websocket.c ├── websocket.h ├── worker.c └── worker.h └── web-src ├── .gitignore ├── .prettierrc.json ├── eslint.config.js ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── logo.svg ├── mstile-150x150.png ├── safari-pinned-tab.svg └── site.webmanifest ├── src ├── App.vue ├── components │ ├── ControlDropdown.vue │ ├── ControlSlider.vue │ ├── CoverArtwork.vue │ ├── IndexButtonList.vue │ ├── ListAlbums.vue │ ├── ListAlbumsSpotify.vue │ ├── ListArtists.vue │ ├── ListArtistsSpotify.vue │ ├── ListComposers.vue │ ├── ListDirectories.vue │ ├── ListGenres.vue │ ├── ListItemQueueItem.vue │ ├── ListPlaylists.vue │ ├── ListPlaylistsSpotify.vue │ ├── ListTracks.vue │ ├── ListTracksSpotify.vue │ ├── LyricsPane.vue │ ├── ModalDialog.vue │ ├── ModalDialogAddRss.vue │ ├── ModalDialogAddUrlStream.vue │ ├── ModalDialogAlbum.vue │ ├── ModalDialogAlbumSpotify.vue │ ├── ModalDialogArtist.vue │ ├── ModalDialogArtistSpotify.vue │ ├── ModalDialogComposer.vue │ ├── ModalDialogDirectory.vue │ ├── ModalDialogGenre.vue │ ├── ModalDialogPlaylist.vue │ ├── ModalDialogPlaylistSave.vue │ ├── ModalDialogPlaylistSpotify.vue │ ├── ModalDialogQueueItem.vue │ ├── ModalDialogRemotePairing.vue │ ├── ModalDialogTrack.vue │ ├── ModalDialogTrackSpotify.vue │ ├── ModalDialogUpdate.vue │ ├── NavbarBottom.vue │ ├── NavbarItemLink.vue │ ├── NavbarItemOutput.vue │ ├── NavbarTop.vue │ ├── NotificationList.vue │ ├── PlayerButtonConsume.vue │ ├── PlayerButtonLyrics.vue │ ├── PlayerButtonNext.vue │ ├── PlayerButtonPlayPause.vue │ ├── PlayerButtonPrevious.vue │ ├── PlayerButtonRepeat.vue │ ├── PlayerButtonSeekBack.vue │ ├── PlayerButtonSeekForward.vue │ ├── PlayerButtonShuffle.vue │ ├── SettingsCheckbox.vue │ ├── SettingsIntfield.vue │ ├── SettingsTextfield.vue │ ├── TabsAudiobooks.vue │ ├── TabsMusic.vue │ ├── TabsSearch.vue │ └── TabsSettings.vue ├── filter │ └── index.js ├── i18n │ ├── de.json │ ├── en.json │ ├── fr.json │ ├── index.js │ ├── zh-CN.json │ └── zh-TW.json ├── icons.js ├── lib │ ├── Audio.js │ ├── GroupedList.js │ └── SVGRenderer.js ├── main.js ├── mystyles.scss ├── pages │ ├── PageAbout.vue │ ├── PageAlbum.vue │ ├── PageAlbumSpotify.vue │ ├── PageAlbums.vue │ ├── PageArtist.vue │ ├── PageArtistSpotify.vue │ ├── PageArtistTracks.vue │ ├── PageArtists.vue │ ├── PageAudiobooksAlbum.vue │ ├── PageAudiobooksAlbums.vue │ ├── PageAudiobooksArtist.vue │ ├── PageAudiobooksArtists.vue │ ├── PageAudiobooksGenres.vue │ ├── PageComposerAlbums.vue │ ├── PageComposerTracks.vue │ ├── PageComposers.vue │ ├── PageFiles.vue │ ├── PageGenreAlbums.vue │ ├── PageGenreTracks.vue │ ├── PageGenres.vue │ ├── PageMusic.vue │ ├── PageMusicRecentlyAdded.vue │ ├── PageMusicRecentlyPlayed.vue │ ├── PageMusicSpotify.vue │ ├── PageMusicSpotifyFeaturedPlaylists.vue │ ├── PageMusicSpotifyNewReleases.vue │ ├── PageNowPlaying.vue │ ├── PagePlaylistFolder.vue │ ├── PagePlaylistTracks.vue │ ├── PagePlaylistTracksSpotify.vue │ ├── PagePodcast.vue │ ├── PagePodcasts.vue │ ├── PageQueue.vue │ ├── PageRadioStreams.vue │ ├── PageSearchLibrary.vue │ ├── PageSearchSpotify.vue │ ├── PageSettingsArtwork.vue │ ├── PageSettingsOnlineServices.vue │ ├── PageSettingsRemotesOutputs.vue │ └── PageSettingsWebinterface.vue ├── router │ └── index.js ├── stores │ ├── configuration.js │ ├── library.js │ ├── lyrics.js │ ├── notifications.js │ ├── outputs.js │ ├── player.js │ ├── queue.js │ ├── remotes.js │ ├── search.js │ ├── services.js │ ├── settings.js │ └── ui.js ├── templates │ ├── ContentWithHeading.vue │ └── ContentWithHero.vue └── webapi │ └── index.js └── vite.config.js /.dev/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: vscode 2 | 3 | vscode: 4 | mkdir -p ../.vscode 5 | cp -rT ./vscode ../.vscode 6 | mkdir -p ../.devcontainer 7 | cp -rT ./devcontainer ../.devcontainer 8 | -------------------------------------------------------------------------------- /.dev/devcontainer/.scripts/init-devcontainer-cli.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # cd aliases 4 | alias ..='cd ..' 5 | alias ...='cd ../..' 6 | alias -- -='cd -' 7 | 8 | # bat aliases 9 | alias bat='batcat' 10 | 11 | if [ "$ENABLE_ESA" = "1" ]; then 12 | if [ "$(command -v eza)" ]; then 13 | alias l='eza -la --icons=auto --group-directories-first' 14 | alias la='eza -la --icons=auto --group-directories-first' 15 | alias ll='eza -l --icons=auto --group-directories-first' 16 | alias l.='eza -d .*' 17 | alias ls='eza' 18 | alias l1='eza -1' 19 | fi 20 | fi 21 | -------------------------------------------------------------------------------- /.dev/devcontainer/.scripts/install-devcontainer-tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Install mkdocs with mkdocs-material theme 4 | pipx install --include-deps mkdocs-material 5 | pipx inject mkdocs-material mkdocs-minify-plugin 6 | 7 | # Starfish (https://starship.rs/) - shell prompt 8 | if [ "$ENABLE_STARSHIP" = "1" ] 9 | then 10 | curl -sS https://starship.rs/install.sh | sh -s -- -y 11 | echo 'eval "$(starship init bash)"' >> ~/.bashrc 12 | fi 13 | 14 | # Atuin (https://atuin.sh/) - shell history 15 | if [ "$ENABLE_ATUIN" = "1" ] 16 | then 17 | curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh 18 | curl https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh -o ~/.bash-preexec.sh 19 | fi 20 | 21 | # zoxide (https://github.com/ajeetdsouza/zoxide) - replacement for cd 22 | if [ "$ENABLE_ZOXIDE" = "1" ] 23 | then 24 | curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh 25 | echo 'eval "$(zoxide init bash)"' >> ~/.bashrc 26 | fi 27 | 28 | pipx install harlequin 29 | pipx install toolong 30 | pipx install posting 31 | -------------------------------------------------------------------------------- /.dev/devcontainer/devcontainer.env: -------------------------------------------------------------------------------- 1 | # Starfish (https://starship.rs/) - shell prompt 2 | ENABLE_STARSHIP=1 3 | 4 | # Atuin (https://atuin.sh/) - shell history 5 | ENABLE_ATUIN=1 6 | 7 | # zoxide (https://github.com/ajeetdsouza/zoxide) - replacement for cd 8 | ENABLE_ZOXIDE=1 9 | 10 | # eza (https://eza.rocks/) - replacement for ls 11 | ENABLE_ESA=1 12 | -------------------------------------------------------------------------------- /.dev/vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/usr/include/json-c" 8 | ], 9 | "defines": [], 10 | "compilerPath": "/usr/bin/clang", 11 | "cStandard": "c17", 12 | "cppStandard": "c++17", 13 | "intelliSenseMode": "linux-clang-x64" 14 | } 15 | ], 16 | "version": 4 17 | } -------------------------------------------------------------------------------- /.dev/vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "OwnTone", 5 | "type": "cppdbg", 6 | "request": "launch", 7 | "program": "${workspaceFolder}/src/owntone", 8 | "args": ["-f", "-c", "/data/conf/owntone.conf", "-w", "${workspaceFolder}/htdocs", "-s", "${workspaceFolder}/sqlext/.libs/owntone-sqlext.so"], 9 | "stopAtEntry": false, 10 | "cwd": "${workspaceFolder}", 11 | "externalConsole": false, 12 | "MIMode": "gdb", 13 | "setupCommands": [ 14 | { 15 | "description": "Enable pretty-printing for gdb", 16 | "text": "-enable-pretty-printing", 17 | "ignoreFailures": true 18 | }, 19 | { 20 | "description": "Set Disassembly Flavor to Intel", 21 | "text": "-gdb-set disassembly-flavor intel", 22 | "ignoreFailures": true 23 | } 24 | ] 25 | } 26 | ], 27 | "version": "2.0.0" 28 | } -------------------------------------------------------------------------------- /.dev/vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.default.forcedInclude": [ 3 | "${workspaceFolder}/config.h" 4 | ], 5 | "[vue]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[c]": { 9 | "editor.detectIndentation": false, 10 | "editor.tabSize": 8 11 | } 12 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug or problem report 3 | about: Use this if OwnTone isn't working the way it should 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please try to provide the following: 11 | 12 | - steps to reproduce and/or logging of the error 13 | - version of owntone 14 | - platform 15 | 16 | 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. Don’t cut and paste lengthy log outtakes here on github. Instead, attach the log file. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other issue 3 | about: Submit a question or a suggestion 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/codeql/codeql-config.yml: -------------------------------------------------------------------------------- 1 | name: "OwnTone CodeQL config" 2 | 3 | paths-ignore: 4 | - htdocs 5 | -------------------------------------------------------------------------------- /.github/workflows/build_htdocs.yml: -------------------------------------------------------------------------------- 1 | name: Build htdocs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'web-src/**' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Install dependencies 17 | working-directory: web-src 18 | run: npm install 19 | 20 | # Build for production with minification (will update web interface 21 | # in "../htdocs") 22 | - name: Build htdocs 23 | working-directory: web-src 24 | run: npm run build 25 | 26 | - name: Count changed files 27 | id: count 28 | run: | 29 | git add htdocs/ 30 | git diff --numstat --staged > diffstat 31 | test -s diffstat || { echo "Warning: Push to web-src did not change htdocs"; exit 1; } 32 | 33 | # The GH action email is from https://github.com/orgs/community/discussions/26560 34 | - name: Commit and push updated assets 35 | run: | 36 | git config --global user.name "github-actions[bot]" 37 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 38 | git commit -m "[web] Rebuild web interface" 39 | git push 40 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - 'docs/**' 9 | - 'htdocs/**' 10 | - 'web-src/**' 11 | pull_request: 12 | branches: 13 | - master 14 | paths-ignore: 15 | - 'docs/**' 16 | - 'htdocs/**' 17 | - 'web-src/**' 18 | schedule: 19 | - cron: '0 19 * * 6' 20 | 21 | jobs: 22 | analyse: 23 | name: Analyse 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v3 33 | with: 34 | config-file: ./.github/codeql/codeql-config.yml 35 | # Override language selection by uncommenting this and choosing your languages 36 | # with: 37 | # languages: go, javascript, csharp, python, cpp, java 38 | 39 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 40 | # If this step fails, then you should remove it and run the build manually (see below) 41 | #- name: Autobuild 42 | # uses: github/codeql-action/autobuild@v3 43 | 44 | # ℹ️ Command-line programs to run using the OS shell. 45 | # 📚 https://git.io/JvXDl 46 | 47 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 48 | # and modify them (or add more) to build your code if your project 49 | # uses a compiled language 50 | - run: | 51 | sudo apt-get update 52 | 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 53 | autoreconf -vi 54 | ./configure --enable-lastfm --enable-chromecast 55 | scan-build --status-bugs -disable-checker deadcode.DeadStores --exclude src/parsers make 56 | 57 | - name: Perform CodeQL Analysis 58 | uses: github/codeql-action/analyze@v3 59 | -------------------------------------------------------------------------------- /.github/workflows/freebsd.yml: -------------------------------------------------------------------------------- 1 | name: FreeBSD 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - 'docs/**' 9 | - 'htdocs/**' 10 | - 'web-src/**' 11 | pull_request: 12 | branches: 13 | - master 14 | paths-ignore: 15 | - 'docs/**' 16 | - 'htdocs/**' 17 | - 'web-src/**' 18 | workflow_dispatch: 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Build 28 | uses: vmactions/freebsd-vm@v1 29 | with: 30 | prepare: | 31 | 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 32 | pw user add owntone -m -d /usr/local/var/cache/owntone 33 | 34 | run: | 35 | autoreconf -vi 36 | export CFLAGS="${ARCH} -g -I/usr/local/include -I/usr/include" 37 | export LDFLAGS="-L/usr/local/lib -L/usr/lib" 38 | ./configure --disable-install-system 39 | gmake 40 | gmake install 41 | mkdir -p /srv/music 42 | service dbus onestart 43 | service avahi-daemon onestart 44 | /usr/local/sbin/owntone -f -t 45 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: build and deploy mkdocs to github pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-python@v2 12 | with: 13 | python-version: 3.x 14 | - run: pip install mkdocs-material 15 | - run: pip install mkdocs-minify-plugin 16 | - run: mkdocs gh-deploy --force 17 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - 'docs/**' 9 | - 'htdocs/**' 10 | - 'web-src/**' 11 | pull_request: 12 | branches: 13 | - master 14 | paths-ignore: 15 | - 'docs/**' 16 | - 'htdocs/**' 17 | - 'web-src/**' 18 | workflow_dispatch: 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Install dependencies 28 | run: | 29 | sudo apt-get update 30 | 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 31 | 32 | - name: Build and check 33 | run: | 34 | autoreconf -vi 35 | ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-install-user --enable-chromecast --with-pulseaudio 36 | make 37 | make check 38 | make distcheck 39 | 40 | - name: Install 41 | run: | 42 | sudo mkdir -p /srv/music 43 | sudo make install 44 | sudo sed -i 's/loglevel = log/loglevel = debug/g' /etc/owntone.conf 45 | 46 | - name: Install and run avahi-daemon 47 | run: | 48 | sudo apt-get install -yq avahi-daemon 49 | 50 | - name: Test run 51 | run: | 52 | sudo /usr/sbin/owntone -f -t 53 | -------------------------------------------------------------------------------- /.github/workflows/webui_lint.yml: -------------------------------------------------------------------------------- 1 | name: Web UI Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'web-src/**' 9 | pull_request: 10 | branches: 11 | - master 12 | paths: 13 | - 'web-src/**' 14 | 15 | jobs: 16 | check: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install dependencies 22 | working-directory: web-src 23 | run: npm install 24 | 25 | - name: Run linter 26 | working-directory: web-src 27 | run: npm run lint --no-fix 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | Makefile.in 4 | Makefile 5 | *.o 6 | *.lo 7 | *.a 8 | *.la 9 | .dirstamp 10 | .deps/ 11 | .libs/ 12 | 13 | # autofoo stuff 14 | autom4te.cache 15 | aclocal.m4 16 | compile 17 | config.guess 18 | config.h 19 | config.h.in 20 | config.log 21 | config.status 22 | config.sub 23 | configure 24 | depcomp 25 | install-sh 26 | libtool 27 | ltmain.sh 28 | missing 29 | stamp-h1 30 | autotools-stamp 31 | build-stamp 32 | ylwrap 33 | owntone.spec 34 | owntone.conf 35 | owntone.service 36 | owntone@.service 37 | 38 | /.settings 39 | /.cproject 40 | /.project 41 | /.autotools 42 | 43 | # ignore MkDocs generated documentation 44 | /site/ 45 | /test/ 46 | 47 | /.vscode/ 48 | /.devcontainer/ 49 | !/.dev/Makefile 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Builds forked-daapd in the latest Ubuntu LTS docker container 2 | # Configuration based on https://romanvm.pythonanywhere.com/post/using-docker-travis-continuous-integration-25/ 3 | 4 | language: c 5 | sudo: required 6 | dist: xenial 7 | 8 | services: 9 | - docker 10 | 11 | env: 12 | matrix: 13 | - SH="docker exec -t ubuntu-lts bash -c" CFG="" 14 | - SH="docker exec -t ubuntu-lts bash -c" CFG="--enable-lastfm --disable-verification" 15 | - SH="docker exec -t ubuntu-lts bash -c" CFG="--enable-spotify --disable-verification" 16 | - SH="docker exec -t ubuntu-lts bash -c" CFG="--enable-chromecast --disable-verification" 17 | - SH="docker exec -t ubuntu-lts bash -c" CFG="--with-pulseaudio --disable-verification" 18 | 19 | before_install: 20 | - docker run -d --name ubuntu-lts -v $(pwd):/travis -w /travis ubuntu:latest tail -f /dev/null 21 | - docker ps 22 | 23 | install: 24 | - $SH "apt-get -qq update" 25 | - $SH "apt-get install -y wget gnupg2" 26 | - $SH "wget -q -O - https://apt.mopidy.com/mopidy.gpg | apt-key add -" 27 | - $SH "wget -q -O /etc/apt/sources.list.d/mopidy.list https://apt.mopidy.com/stretch.list" 28 | - $SH "apt-get -qq update" 29 | - $SH "DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential clang clang-tools git autotools-dev autoconf libtool gettext gawk gperf antlr3 libantlr3c-dev 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 libspotify-dev" 30 | 31 | script: 32 | - $SH "autoreconf -fi" 33 | - $SH "./configure $CFG" 34 | - $SH "make" 35 | - $SH "make clean" 36 | - $SH "scan-build --status-bugs -disable-checker deadcode.DeadStores make" 37 | 38 | # Disable email notification 39 | notifications: 40 | email: false 41 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | OwnTone is maintained by: 2 | 3 | - Espen Jürgensen 4 | - Christian Meffert 5 | 6 | Up until version 27.4 it was called forked-daapd. 7 | 8 | forked-daapd up until 0.19 was written by Julien BLACHE . It was 9 | build on mt-daapd (Firefly Media Server), which was originally written by Ron 10 | Pedde and a handful of contributors. 11 | 12 | For a complete list of contributers, please see: 13 | https://github.com/owntone/owntone-server/graphs/contributors 14 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installation instructions for OwnTone 2 | 3 | Instructions on how to install OwnTone can be found at 4 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Sorry, you'll have to find your news elsewhere. This is just a boilerplate to 2 | satisfy automake. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OwnTone 2 | 3 | OwnTone is a media server that lets you play audio sources such as local files, 4 | Spotify, pipe input or internet radio to AirPlay 1 and 2 receivers, Chromecast 5 | receivers, Roku Soundbridge, a browser or the server’s own sound system. Or you 6 | can listen to your music via any client that supports mp3 streaming. 7 | 8 | You control the server via a web interface, Apple Remote, an Android remote 9 | (e.g. Retune), an MPD client, json API or DACP. 10 | 11 | OwnTone also serves local files via the Digital Audio Access Protocol (DAAP) to 12 | iTunes (Windows), Apple Music (macOS) and Rhythmbox (Linux), and via the Roku 13 | Server Protocol (RSP) to Roku devices. 14 | 15 | Runs on Linux, BSD and macOS. 16 | 17 | OwnTone was previously called forked-daapd, which again was a rewrite of 18 | mt-daapd (Firefly Media Server). 19 | 20 | 21 | ## Looking for help? 22 | 23 | Visit the [OwnTone documentation](https://owntone.github.io/owntone-server/) for 24 | usage and set up instructions, API documentation, etc. 25 | 26 | If you are looking for information on how to get and install OwnTone, then see 27 | the [Installation](https://owntone.github.io/owntone-server/installation/) 28 | instructions. 29 | -------------------------------------------------------------------------------- /UPGRADING: -------------------------------------------------------------------------------- 1 | Upgrading 2 | --------- 3 | 4 | From time to time, newer versions of OwnTone may need to perform a database 5 | upgrade. This upgrade is handled by the server upon startup if required. 6 | 7 | Before upgrading the server, it is always a good idea to backup your database, 8 | just in case. 9 | 10 | The database upgrade procedure is built into the server; there is no external 11 | upgrade script to run. Depending on the changes done to the database structure, 12 | the upgrade process will take more or less time and may need some space in /tmp 13 | for temporary data. The upgrade can also require some more space in the 14 | directory containing the database file. 15 | 16 | Before running the new server version, make sure you have done your backups and 17 | checked your disk space. 18 | 19 | Some upgrades can also trigger a full rescan to rebuild parts of the database, 20 | so startup will be a bit slower and more resource-intensive than usual. 21 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /build-aux/config.rpath: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/build-aux/config.rpath -------------------------------------------------------------------------------- /docs/advanced/multiple-instances.md: -------------------------------------------------------------------------------- 1 | # Running Multiple Instances 2 | 3 | To run multiple instances of OwnTone on a server, you should copy 4 | `/etc/owntone.conf` to `/etc/owntone-zone.conf` (for each `zone`) and 5 | modify the following to be unique across all instances: 6 | 7 | * the three port settings (`general` -> `websocket_port`, 8 | `library` -> `port`, and `mpd` -> `port`) 9 | 10 | * the database paths (`general` -> `db_path`, `db_backup_path`, and `db_cache_path`) 11 | 12 | * the service name (`library` -> `name`). 13 | 14 | * you probably also want to disable local output (set `audio` -> `type = 15 | "disabled"`). 16 | 17 | Then run `owntone -c /etc/owntone-zone.conf` to run owntone with the new 18 | zone configuration. 19 | 20 | OwnTone has a `systemd` template which lets you run this automatically 21 | on systems that use systemd. You can start or enable the service for 22 | a `zone` by `sudo systemctl start owntone@zone` and check that it is 23 | running with `sudo systemctl status owntone@zone`. Use `sudo 24 | systemctl enable owntone@zone` to get the service to start on reboot. 25 | -------------------------------------------------------------------------------- /docs/advanced/remote-access.md: -------------------------------------------------------------------------------- 1 | # Remote Access 2 | 3 | It is possible to access a shared library over the internet from a DAAP client 4 | like iTunes. You must have remote access to the host machine. 5 | 6 | First log in to the host and forward port 3689 to your local machine. You now 7 | need to broadcast the DAAP service to iTunes on your local machine. On macOS the 8 | command is: 9 | 10 | ```shell 11 | dns-sd -P iTunesServer _daap._tcp local 3689 localhost.local 127.0.0.1 "ffid=12345" 12 | ``` 13 | 14 | The `ffid` key is required but its value does not matter. 15 | 16 | Your library will now appear as 'iTunesServer' in iTunes. 17 | 18 | You can also access your library remotely using something like Zerotier. See [this 19 | guide](https://github.com/owntone/owntone-server/wiki/Accessing-Owntone-remotely-through-iTunes-Music-with-Zerotier) 20 | for details. 21 | 22 | ## Accessing from Internet for authenticated users 23 | 24 | If you intend to access OwnTone directly from Internet, it is recommended to 25 | protect it against unauthenticated users. 26 | 27 | [This guide](https://blog.cyril.by/en/software/example-sso-with-authelia-and-owntone) 28 | has a detailed setup tutorial to achieve this securely. 29 | -------------------------------------------------------------------------------- /docs/artwork.md: -------------------------------------------------------------------------------- 1 | # Artwork 2 | 3 | OwnTone has support for PNG and JPEG artwork which is either: 4 | 5 | - embedded in the media files 6 | - placed as separate image files in the library 7 | - made available online by the radio station 8 | 9 | For media in your library, OwnTone will try to locate album and artist 10 | artwork (group artwork) by the following procedure: 11 | 12 | - if a file {artwork,cover,Folder}.{png,jpg} is found in one of the directories 13 | containing files that are part of the group, it is used as the artwork. The 14 | first file found is used, ordering is not guaranteed; 15 | - failing that, if [directory name].{png,jpg} is found in one of the 16 | directories containing files that are part of the group, it is used as the 17 | artwork. The first file found is used, ordering is not guaranteed; 18 | - failing that, individual files are examined and the first file found 19 | with an embedded artwork is used. Here again, ordering is not guaranteed. 20 | 21 | {artwork,cover,Folder} are the default, you can add other base names in the 22 | configuration file. Here you can also enable/disable support for individual 23 | file artwork (instead of using the same artwork for all tracks in an entire 24 | album). 25 | 26 | For playlists in your library, say /foo/bar.m3u, then for any http streams in 27 | the list, OwnTone will look for /foo/bar.{jpg,png}. 28 | 29 | You can use symlinks for the artwork files. 30 | 31 | OwnTone caches artwork in a separate cache file. The default path is 32 | `/var/cache/owntone/cache.db` and can be configured in the configuration 33 | file. The cache.db file can be deleted without losing the library and pairing 34 | informations. 35 | -------------------------------------------------------------------------------- /docs/assets/extra.css: -------------------------------------------------------------------------------- 1 | /* 2 | * MkDocs Material theme buttons have wrong colors for the theme 3 | * setting: 4 | * 5 | * palette: 6 | * scheme: default 7 | * primary: white 8 | * 9 | * Thus the following CSS overwrites the button background- and 10 | * text-colors. 11 | */ 12 | .md-button { 13 | color: var(--md-accent-fg-color) !important; 14 | border-color: currentColor !important; 15 | } 16 | .md-button:focus, 17 | .md-button:hover { 18 | color: var(--md-primary-fg-color) !important; 19 | background-color: var(--md-accent-fg-color) !important; 20 | border-color: var(--md-accent-fg-color) !important; 21 | } 22 | 23 | /* 24 | * The nav title has a click handler to scroll to the top 25 | */ 26 | .md-header-nav__title { 27 | cursor: pointer; 28 | } 29 | 30 | /* 31 | * Text alignment 32 | */ 33 | .text-center { 34 | text-align: center; 35 | } 36 | 37 | /* 38 | * Custom CSS for images 39 | */ 40 | .hidden { 41 | visibility: hidden; 42 | opacity: 0; 43 | transition: visibility 0s linear 300ms, opacity 300ms; 44 | } 45 | .visible { 46 | visibility: visible; 47 | opacity: 1; 48 | transition: visibility 0s linear 0s, opacity 300ms; 49 | } 50 | .fullscreen-image-background { 51 | z-index: 25; 52 | background-color: rgba(10, 10, 10, 0.5); 53 | position: fixed; 54 | bottom: 0; 55 | left: 0; 56 | right: 0; 57 | top: 0; 58 | } 59 | .fullscreen-image-background img { 60 | transition: opacity 1s; 61 | cursor: zoom-out; 62 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 63 | z-index: 100; 64 | position: fixed; 65 | left: 0; 66 | right: 0; 67 | top: 0; 68 | bottom: 0; 69 | margin: 1rem auto; 70 | max-height: calc(100vh - 2rem); 71 | width: auto; 72 | } 73 | .zoom { 74 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 75 | border-radius: 0.2rem; 76 | margin: 0.5rem; 77 | width: 25%; 78 | cursor: zoom-in; 79 | } 80 | .zoom-wrapper { 81 | display: flex; 82 | flex-direction: row; 83 | align-items: center; 84 | justify-content: center; 85 | flex-wrap: wrap; 86 | } 87 | -------------------------------------------------------------------------------- /docs/assets/extra.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Add click event handler for images with class="zoom" 3 | */ 4 | document.querySelectorAll('img.zoom').forEach(item => { 5 | const p = item.parentElement; 6 | if (!p.classList.contains('processed')) { 7 | p.classList.add('processed'); 8 | if (p.querySelectorAll('img.zoom').length === p.children.length) { 9 | p.classList.add('zoom-wrapper'); 10 | } 11 | } 12 | item.addEventListener('click', function () { 13 | const img = document.getElementById('fullscreen-image-img'); 14 | img.setAttribute('src', this.getAttribute('src')); 15 | img.setAttribute('alt', this.getAttribute('alt')); 16 | 17 | const div = document.getElementById('fullscreen-image'); 18 | div.classList.replace('hidden', 'visible'); 19 | }) 20 | }); 21 | 22 | var div = document.createElement('div'); 23 | div.classList.add('fullscreen-image-background', 'hidden'); 24 | div.id = 'fullscreen-image'; 25 | var img = document.createElement('img'); 26 | img.id = 'fullscreen-image-img'; 27 | div.appendChild(img); 28 | 29 | div.addEventListener('click', function () { 30 | this.classList.replace('visible', 'hidden'); 31 | }); 32 | document.body.appendChild(div); -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/images/screenshot-audiobooks-authors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-audiobooks-authors.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-audiobooks-books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-audiobooks-books.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-files.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-menu.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-music-album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-music-album.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-music-albums-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-music-albums-options.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-music-albums.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-music-albums.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-music-artist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-music-artist.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-music-artists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-music-artists.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-music-browse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-music-browse.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-music-spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-music-spotify.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-now-playing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-now-playing.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-outputs.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-podcast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-podcast.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-podcasts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-podcasts.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-queue.png -------------------------------------------------------------------------------- /docs/assets/images/screenshot-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/docs/assets/images/screenshot-search.png -------------------------------------------------------------------------------- /docs/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 15 | 18 | 20 | 22 | 24 | 26 | 27 | 28 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/audio-outputs/airplay.md: -------------------------------------------------------------------------------- 1 | # AirPlay devices/speakers 2 | 3 | OwnTone will discover the AirPlay devices available on your network. For 4 | devices that are password-protected, the device's AirPlay name and password 5 | must be given in the configuration file. See the sample configuration file 6 | for the syntax. 7 | 8 | If your Apple TV requires device verification (always required by Apple TV4 with 9 | tvOS 10.2) then you can do that through Settings > Remotes & Outputs in the web 10 | interface: Select the device and then enter the PIN that the Apple TV displays. 11 | 12 | If your speaker is silent when you start playback, and there is no obvious error 13 | message in the log, you can try disabling ipv6 in the config. Some speakers 14 | announce that they support ipv6, but for some reason don't work with OwnTone. 15 | 16 | If the speaker becomes unselected when you start playback, and you in the log 17 | see "ANNOUNCE request failed in session startup: 400 Bad Request", then try 18 | the Apple Home app > Allow Speakers & TV Access > Anyone On the Same Network 19 | (or Everyone). 20 | -------------------------------------------------------------------------------- /docs/audio-outputs/chromecast.md: -------------------------------------------------------------------------------- 1 | # Chromecast 2 | 3 | OwnTone will discover Chromecast devices available on your network, and you 4 | can then select the device as a speaker. There is no configuration required. 5 | 6 | Take note that: 7 | 8 | - Chromecast playback can't be precisely sync'ed with other outputs e.g. AirPlay 9 | - Playback to Google Nest Hub doesn't work (Nest Mini does work) 10 | -------------------------------------------------------------------------------- /docs/audio-outputs/local-audio.md: -------------------------------------------------------------------------------- 1 | # Local audio 2 | 3 | ## Local audio through ALSA 4 | 5 | In the config file, you can select ALSA for local audio. This is the default. 6 | 7 | When using ALSA, the server will try to synchronize playback with AirPlay. You 8 | can adjust the synchronization in the config file. 9 | 10 | For most setups the default values in the config file should work. If they 11 | don't, there is help [here](../advanced/outputs-alsa.md) 12 | 13 | ## Local audio, Bluetooth and more through PulseAudio 14 | 15 | In the config file, you can select PulseAudio for local audio. In addition to 16 | local audio, PulseAudio also supports an array of other targets, e.g. Bluetooth 17 | or DLNA. However, PulseAudio does require some setup, so here is a separate page 18 | with some help on that: [PulseAudio](../advanced/outputs-pulse.md) 19 | 20 | Note that if you select PulseAudio the "card" setting in the config file has 21 | no effect. Instead all sound cards detected by PulseAudio will be listed as 22 | speakers by OwnTone. 23 | 24 | You can adjust the latency of PulseAudio playback in the config file. 25 | -------------------------------------------------------------------------------- /docs/audio-outputs/mobile.md: -------------------------------------------------------------------------------- 1 | # Listening on your Mobile Device 2 | 3 | ## iOS 4 | 5 | On iOS, the options are limited because there are no Media Client apps with DAAP 6 | support and because Apple doesn't allow AirPlay receiver apps. OwnTone also 7 | can't share music via Home Sharing, which is the protocol Apple uses for sharing 8 | media between devices. 9 | 10 | That leaves the following options, which all rely on OwnTone's streaming: 11 | 12 | - listen via the [web interface](web.md) 13 | - use a [MPD client app](../control-clients/mobile.md#mpd-client-apps) that supports local playback 14 | - connect to the [streaming](streaming.md) endpoint with a media player like VLC 15 | 16 | ## Android 17 | 18 | On Android, you can use the same streaming methods described for iOS, but you 19 | can also find apps that act as AirPlay receivers. 20 | -------------------------------------------------------------------------------- /docs/audio-outputs/roku.md: -------------------------------------------------------------------------------- 1 | # Roku devices/speakers 2 | 3 | OwnTone can stream audio to classic RSP/RCP-based devices like Roku Soundbridge 4 | M1001 and M2000. 5 | 6 | If the source file is in a non-supported format, like flac, OwnTone will 7 | transcode to wav. Transmitting wav requires some bandwidth and the legacy 8 | network interfaces of these devices may struggle with that. If so, you can 9 | change the transcoding format for the speaker to alac via the [JSON API](../json-api.md#change-an-output). 10 | -------------------------------------------------------------------------------- /docs/audio-outputs/streaming.md: -------------------------------------------------------------------------------- 1 | # Streaming 2 | 3 | The streaming option is useful when you want to listen to audio played by 4 | OwnTone in a browser or a media player of your choice [^1],[^2]. 5 | 6 | You can control playback via the web interface or any of the supported control 7 | clients. 8 | 9 | ## Listening to Audio in a Media Player 10 | 11 | To listen to audio being played by OwnTone in a media player, follow these 12 | steps: 13 | 14 | 1. Start playing audio in OwnTone. 15 | 2. In the web interface, activate the stream in the output menu by clicking 16 | on the icon :material-broadcast: next to HTTP Stream. 17 | After a few seconds, the audio should play in the background. 18 | 3. Copy the URL behind the :material-open-in-new: icon next to HTTP Stream. 19 | 4. Open the copied URL with the media player, e.g., VLC. 20 | The URL is usually 21 | [http://owntone.local:3689/stream.mp3](http://owntone.local:3689/stream.mp3) 22 | or http://SERVER_ADDRESS:3689/stream.mp3 23 | 24 | ## Notes 25 | 26 | [^1]: On iOS devices, the streaming option is the only way of listening to your 27 | audio, since Apple does not allow AirPlay receiver apps, and because 28 | Home Sharing cannot be supported by OwnTone. 29 | 30 | [^2]: For the streaming option to work, MP3 encoding must be supported by 31 | `libavcodec`. If it is not, a message will appear in the log file. 32 | For example, on Debian or Ubuntu, MP3 encoding support is provided by the 33 | package `libavcodec-extra`. 34 | -------------------------------------------------------------------------------- /docs/audio-outputs/web.md: -------------------------------------------------------------------------------- 1 | # Listening to Audio in a Browser 2 | 3 | To listen to audio being played by OwnTone in a browser, follow these 4 | steps: 5 | 6 | 1. Start playing audio in OwnTone. 7 | 2. In the web interface, activate the stream in the output menu by clicking 8 | on the icon :material-broadcast: next to HTTP Stream. 9 | After a few seconds, the audio should play in the background [^1]. 10 | 11 | ![Outputs](../assets/images/screenshot-outputs.png){: class="zoom" } 12 | 13 | For the streaming option to work, MP3 encoding must be supported by 14 | `libavcodec`. If it is not, a message will appear in the log file. For example, 15 | on Debian or Ubuntu, MP3 encoding support is provided by the package 16 | `libavcodec-extra`. 17 | 18 | [^1]: On iOS devices, playing audio in the background when the device is locked 19 | is not supported in a private browser tab. 20 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | --8<-- "ChangeLog" -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | The configuration of OwnTone is usually located in `/etc/owntone.conf`. 4 | 5 | ## Format 6 | 7 | Each setting consists of a name and a value. There are different types of settings: string, integer, boolean, and list. 8 | 9 | Comments are preceded by a hash sign. 10 | 11 | The format is as follow: 12 | 13 | ```conf 14 | # Section 15 | section { 16 | # String value 17 | setting = "" 18 | # Integer value 19 | setting = 20 | # Boolean 21 | setting = 22 | # List 23 | setting = { "value a", "value b", "value n"} 24 | } 25 | ``` 26 | 27 | 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: 28 | 29 | ```conf 30 | airplay "Jared's Room" { 31 | max_volume = 3 32 | } 33 | ``` 34 | 35 | ## Most important settings 36 | 37 | ### general: uid 38 | 39 | Identifier of the user running OwnTone. 40 | 41 | 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. 42 | 43 | ### library: directories 44 | 45 | Path to the directory or directories containing the media to index (your library). 46 | 47 | ## Other settings 48 | 49 | 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. 50 | -------------------------------------------------------------------------------- /docs/control-clients/cli-api.md: -------------------------------------------------------------------------------- 1 | # API and Command Line 2 | 3 | You can choose between: 4 | 5 | - [The JSON API](#json-api) 6 | - [A MPD command line client like mpc](#mpc) 7 | - [DAAP/DACP commands](#daapdacp) 8 | 9 | The JSON API is the most versatile and the recommended method, but for simple 10 | command line operations, mpc is easier. DAAP/DACP is only for masochists. 11 | 12 | 13 | ## JSON API 14 | 15 | See the [JSON API docs](../json-api.md) 16 | 17 | 18 | ## mpc 19 | 20 | [mpc](https://www.musicpd.org/clients/mpc/) is easy to use for simple operations 21 | like enabling speakers, changing volume and getting status. 22 | 23 | Due to differences in implementation between OwnTone and MPD, some mpc commands 24 | will work differently or not at all. 25 | 26 | 27 | ## DAAP/DACP 28 | 29 | Here is an example of how to use curl with DAAP/DACP. Say you have a playlist 30 | with a radio station, and you want to make a script that starts playback of that 31 | station: 32 | 33 | 1. Run `sqlite3 [your OwnTone db]`. Use `select id,title from files` to get 34 | the id of the radio station, and use `select id,title from playlists` to get 35 | the id of the playlist. 36 | 2. Convert the two ids to hex. 37 | 3. Put the following lines in the script with the relevant ids inserted (also 38 | observe that you must use a session-id < 100, and that you must login and 39 | logout): 40 | 41 | ```shell 42 | curl "http://localhost:3689/login?pairing-guid=0x1&request-session-id=50" 43 | 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" 44 | curl "http://localhost:3689/logout?session-id=50" 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/control-clients/desktop.md: -------------------------------------------------------------------------------- 1 | # Desktop Remote Control 2 | 3 | To control OwnTone from Linux, Windows or Mac, you can use: 4 | 5 | - [The web interface](#the-web-interface) 6 | - [A remote for iTunes/Apple Music](#remotes-for-itunesapple-music) 7 | - [A MPD client](#mpd-clients) 8 | 9 | The web interface is the most feature complete and works on all platforms, so 10 | on desktop there isn't much reason to use anything else. 11 | 12 | However, instead of a remote control application, you can also connect to 13 | OwnTone via a Media Client e.g. iTunes or Apple Music. Media clients will get 14 | the media from OwnTone and do the playback themselves (remotes just control 15 | OwnTone playback). See [Media Clients](../media-clients.md) for more 16 | information. 17 | 18 | 19 | ## The web interface 20 | 21 | See [web interface](web.md). 22 | 23 | 24 | ## Remotes for iTunes/Apple Music 25 | 26 | There are only a few of these, see the below table. 27 | 28 | | Client | Developer | Type | Platform | Working (vers.) | 29 | | ------------------------ | ----------- | ------ | --------------- | --------------- | 30 | | TunesRemote SE | | Remote | Java | Yes (r108) | 31 | | rtRemote for Windows | bizmodeller | Remote | Windows | Yes (1.2.0.67) | 32 | 33 | 34 | ## MPD clients 35 | 36 | There's a range of MPD clients available that also work with OwnTone e.g. 37 | Cantata and Plattenalbum. 38 | 39 | The better ones support local playback, speaker control, artwork and automatic 40 | discovery of OwnTone's MPD server. 41 | 42 | By default OwnTone listens on port 6600 for MPD clients. You can change 43 | this in the configuration file. 44 | 45 | Due to some differences between OwnTone and MPD not all commands will act the 46 | same way they would running MPD: 47 | 48 | - crossfade, mixrampdb, mixrampdelay and replaygain will have no effect 49 | - single, repeat: unlike MPD, OwnTone does not support setting single and repeat 50 | separately on/off, instead repeat off, repeat all and repeat single are 51 | supported. Thus setting single on will result in repeat single, repeat on 52 | results in repeat all. 53 | -------------------------------------------------------------------------------- /docs/control-clients/web.md: -------------------------------------------------------------------------------- 1 | # Web Interface 2 | 3 | The built-in web interface is a mobile-friendly music player and browser for 4 | OwnTone. 5 | 6 | You can reach it at [http://owntone.local:3689](http://owntone.local:3689) 7 | or depending on the OwnTone installation at `http://:`. 8 | 9 | This interface becomes useful when you need to control playback, trigger 10 | manual library rescans, pair with remotes, select speakers, grant access to 11 | Spotify, and for many other operations. 12 | 13 | Alternatively, you can use a MPD web client like for instance [ympd](http://www.ympd.org/). 14 | 15 | 16 | ## Screenshots 17 | 18 | Below you have a selection of screenshots that shows different part of the 19 | interface. 20 | 21 | ![Now playing](../assets/images/screenshot-now-playing.png){: class="zoom" } 22 | ![Queue](../assets/images/screenshot-queue.png){: class="zoom" } 23 | ![Music browse](../assets/images/screenshot-music-browse.png){: class="zoom" } 24 | ![Music artists](../assets/images/screenshot-music-artists.png){: class="zoom" } 25 | ![Music artist](../assets/images/screenshot-music-artist.png){: class="zoom" } 26 | ![Music albums](../assets/images/screenshot-music-albums.png){: class="zoom" } 27 | ![Music albums options](../assets/images/screenshot-music-albums-options.png){: class="zoom" } 28 | ![Music album](../assets/images/screenshot-music-album.png){: class="zoom" } 29 | ![Spotify](../assets/images/screenshot-music-spotify.png){: class="zoom" } 30 | ![Audiobooks authors](../assets/images/screenshot-audiobooks-authors.png){: class="zoom" } 31 | ![Audiobooks](../assets/images/screenshot-audiobooks-books.png){: class="zoom" } 32 | ![Podcasts](../assets/images/screenshot-podcasts.png){: class="zoom" } 33 | ![Podcast](../assets/images/screenshot-podcast.png){: class="zoom" } 34 | ![Files](../assets/images/screenshot-files.png){: class="zoom" } 35 | ![Search](../assets/images/screenshot-search.png){: class="zoom" } 36 | ![Menu](../assets/images/screenshot-menu.png){: class="zoom" } 37 | ![Outputs](../assets/images/screenshot-outputs.png){: class="zoom" } 38 | 39 | 40 | ## Usage 41 | 42 | The web interface is usually reachable at [http://owntone.local:3689](http://owntone.local:3689). 43 | But depending on the setup of OwnTone you might need to adjust the server name 44 | and port of the server accordingly `http://:`. 45 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | After installation (see [Installation](installation.md)) do the following: 4 | 5 | 1. Edit the configuration file (usually `/etc/owntone.conf`) to suit your 6 | needs 7 | 2. Start or restart the server (usually `/etc/init.d/owntone restart`) 8 | 3. Go to the web interface [http://owntone.local:3689](http://owntone.local:3689), 9 | or, if that won't work, to http://SERVER_ADDRESS:3689 10 | 4. Wait for the library scan to complete 11 | 5. If you will be using a remote, e.g. Apple Remote: Start the remote, go to 12 | Settings, Add Library 13 | 6. Enter the pair code in the web interface (update the page with F5 if it does 14 | not automatically pick up the pairing request) 15 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # How to get and install OwnTone 2 | 3 | You can compile and run OwnTone on pretty much any Linux- or BSD-platform. The 4 | instructions are [here](building.md). 5 | 6 | Apt repositories, images and precompiled binaries are available for some 7 | platforms. These can save you some work and make it easier to stay up to date: 8 | 9 | |Platform | How to get 10 | |----------------------|--------------------------------------------------------- 11 | |RPi w/Raspberry Pi OS | Add OwnTone repository to apt sources
(See: [Raspberry Pi Forums](http://www.raspberrypi.org/phpBB3/viewtopic.php?t=49928)) 12 | |Debian/Ubuntu amd64 | Download the .deb package as artifact from the [Github workflow](https://github.com/owntone/owntone-apt/actions)
(requires that you are logged in) 13 | |OpenWrt | Run `opkg install libwebsockets-full owntone` 14 | |Docker / Podman | See [official image](https://github.com/owntone/owntone-container) 15 | |FreeBSD | Run `pkg install owntone` (See: [FreeBSD ports](https://cgit.freebsd.org/ports/tree/audio/owntone)) 16 | 17 | OwnTone is not in the official Debian repositories due to lack of Debian 18 | maintainer and Debian policy difficulties concerning the web UI, see 19 | [this issue](https://github.com/owntone/owntone-server/issues/552). 20 | -------------------------------------------------------------------------------- /docs/integrations/lastfm.md: -------------------------------------------------------------------------------- 1 | # LastFM 2 | 3 | You can have OwnTone scrobble the music you listen to. To set up scrobbling 4 | go to the web interface and authorize OwnTone with your LastFM credentials. 5 | 6 | OwnTone will not store your LastFM username/password, only the session key. 7 | The session key does not expire. 8 | -------------------------------------------------------------------------------- /docs/media-clients.md: -------------------------------------------------------------------------------- 1 | # Media Clients 2 | 3 | Media Clients are applications that download the media from the server and do 4 | the playback themselves. OwnTone supports media clients via the DAAP and RSP 5 | protocols (so not UPNP). 6 | 7 | Some Media Clients are also able to play video from OwnTone. 8 | 9 | OwnTone can't serve Spotify, internet radio and streams to Media Clients. For 10 | that you must let OwnTone do the playback. 11 | 12 | Here is a list of working and non-working DAAP clients. The list is probably 13 | obsolete when you read it :-) 14 | 15 | | Client | Developer | Type | Platform | Working (vers.) | 16 | | ------------------------ | ----------- | ------ | --------------- | --------------- | 17 | | iTunes | Apple | DAAP | Win | Yes (12.10.1) | 18 | | Apple Music | Apple | DAAP | macOS | Yes | 19 | | Rhythmbox | Gnome | DAAP | Linux | Yes | 20 | | Diapente | diapente | DAAP | Android | Yes | 21 | | WinAmp DAAPClient | WardFamily | DAAP | WinAmp | Yes | 22 | | Amarok w/DAAP plugin | KDE | DAAP | Linux/Win | Yes (2.8.0) | 23 | | Banshee | | DAAP | Linux/Win/macOS | No (2.6.2) | 24 | | jtunes4 | | DAAP | Java | No | 25 | | Firefly Client | | (DAAP) | Java | No | 26 | 27 | Technically, devices like the Roku Soundbridge are both media clients and 28 | audio outputs. You can find information about them [here](audio-outputs/roku.md). 29 | -------------------------------------------------------------------------------- /docs/playlists.md: -------------------------------------------------------------------------------- 1 | # Playlists and Radio 2 | 3 | OwnTone supports M3U and PLS playlists. Just drop your playlist somewhere 4 | in your library with an .m3u or .pls extension and it will pick it up. 5 | 6 | From the web interface, and some mpd clients, you can also create and modify 7 | playlists by saving the current queue. Click the "Save" button. Note that this 8 | requires that `allow_modifying_stored_playlists` is enabled in the configuration 9 | file, and that the server has write access to `default_playlist_directory`. 10 | 11 | If the playlist contains an http URL it will be added as an internet radio 12 | station, and the URL will be probed for Shoutcast (ICY) metadata. If the radio 13 | station provides artwork, OwnTone will download it during playback and send 14 | it to any remotes or AirPlay devices requesting it. 15 | 16 | Instead of downloading M3U's from your radio stations, you can also make an 17 | empty M3U file and insert links in it to the M3U's of your radio stations. 18 | 19 | Radio streams can only be played by OwnTone, so that means they will not be 20 | available to play in DAAP clients like iTunes. 21 | 22 | The server can import playlists from iTunes Music Library XML files. By default, 23 | metadata from our parsers is preferred over what's in the iTunes DB; use 24 | itunes_overrides = true if you prefer iTunes' metadata. 25 | 26 | OwnTone has support for smart playlists. How to create a smart playlist is 27 | documented in [Smart playlists](smart-playlists.md). 28 | 29 | If you're not satisfied with internet radio metadata that OwnTone shows, 30 | then you can read about tweaking it in 31 | [Radio streams](advanced/radio-streams.md). 32 | -------------------------------------------------------------------------------- /htdocs/Makefile.am: -------------------------------------------------------------------------------- 1 | if COND_WEBINTERFACE 2 | WEBINTERFACE_SRC = \ 3 | index.html 4 | endif 5 | 6 | 7 | htdocsdir = $(datadir)/owntone/htdocs 8 | 9 | dist_htdocs_DATA = \ 10 | $(WEBINTERFACE_SRC) \ 11 | android-chrome-192x192.png \ 12 | android-chrome-512x512.png \ 13 | apple-touch-icon.png \ 14 | browserconfig.xml \ 15 | favicon.ico \ 16 | favicon-16x16.png \ 17 | favicon-32x32.png \ 18 | mstile-150x150.png \ 19 | safari-pinned-tab.svg \ 20 | site.webmanifest 21 | 22 | if COND_WEBINTERFACE 23 | htdocsassetsdir = $(datadir)/owntone/htdocs/assets 24 | 25 | dist_htdocsassets_DATA = \ 26 | assets/index.css \ 27 | assets/index.js 28 | endif 29 | -------------------------------------------------------------------------------- /htdocs/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/htdocs/android-chrome-192x192.png -------------------------------------------------------------------------------- /htdocs/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/htdocs/android-chrome-512x512.png -------------------------------------------------------------------------------- /htdocs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/htdocs/apple-touch-icon.png -------------------------------------------------------------------------------- /htdocs/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /htdocs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/htdocs/favicon-16x16.png -------------------------------------------------------------------------------- /htdocs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/htdocs/favicon-32x32.png -------------------------------------------------------------------------------- /htdocs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/htdocs/favicon.ico -------------------------------------------------------------------------------- /htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | OwnTone 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /htdocs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 15 | 18 | 20 | 22 | 24 | 26 | 27 | 28 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /htdocs/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/htdocs/mstile-150x150.png -------------------------------------------------------------------------------- /htdocs/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OwnTone", 3 | "short_name": "OwnTone", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /m4/.gitignore: -------------------------------------------------------------------------------- 1 | libtool.m4 2 | lt*.m4 3 | 4 | -------------------------------------------------------------------------------- /mkdocs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Run mkdocs commands 4 | # 5 | # No local installation of mkdocs and the required plugins is required. 6 | # Instead this script uses the offical docker image for "Material for MkDocs" 7 | # 8 | # https://squidfunk.github.io/mkdocs-material/getting-started/#with-docker 9 | # 10 | # Without arguments the "serve" command is executed (starts the local development 11 | # server). 12 | # 13 | # Usage examples: 14 | # 15 | # - Build documentation: ./mkdocs.sh build 16 | # - Show command help: ./mkdocs.sh --help 17 | 18 | docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material $@ -------------------------------------------------------------------------------- /owntone.8: -------------------------------------------------------------------------------- 1 | .\" -*- nroff -*- 2 | .TH OWNTONE "8" "2018-01-14" "owntone" "DAAP, MPD, Chromecast & RSP media server" 3 | .SH NAME 4 | OwnTone \- iTunes\-compatible DAAP server with MPD, Chromecast and RSP support 5 | .SH SYNOPSIS 6 | .B OwnTone 7 | [\fIoptions\fR] 8 | .SH DESCRIPTION 9 | \fBOwnTone\fP is a Linux/FreeBSD DAAP (iTunes) media server with support 10 | for AirPlay devices, Apple Remote (and compatibles), MPD, Spotify, Chromecast, 11 | mp3 streaming and internet radio. It allows you to share your music collection 12 | over the local network. 13 | .SH OPTIONS 14 | .TP 15 | \fB\-d, \-\-debug=\fR\fIlevel\fP 16 | Log level (0\-5). 17 | .TP 18 | \fB\-D, \-\-logdomains=\fR\fIdom,..,dom\fP 19 | Debug domains; available domains are: \fIconfig\fP, \fIdaap\fP, 20 | \fIdb\fP, \fIhttpd\fP, \fImain\fP, \fImdns\fP, \fImisc\fP, 21 | \fIrsp\fP, \fIscan\fP, \fIxcode\fP, \fIevent\fP, \fIhttp\fP, \fIremote\fP, 22 | \fIdacp\fP, \fIffmpeg\fP, \fIartwork\fP, \fIplayer\fP, \fIraop\fP, 23 | \fIlaudio\fP, \fIdmap\fP, \fIfdbperf\fP, \fIspotify\fP, \fIscrobble\fP, 24 | \fIcache\fP, \fImpd\fP, \fIstream\fP, \fIcast\fP, \fIfifo\fP, \fIlib\fP, 25 | \fIweb\fP, \fIairplay\fP. 26 | .TP 27 | \fB\-c, \-\-config=\fR\fIfile\fP 28 | Use \fIfile\fP as the configuration file. 29 | .TP 30 | \fB\-P, \-\-pidfile=\fR\fIfile\fP 31 | Write PID to \fIfile\fP. 32 | .TP 33 | \fB\-f, \-\-foreground\fR 34 | Run in the foreground. 35 | .TP 36 | \fB\-b, \-\-ffid=\fR\fIffid\fP 37 | \fIffid\fP to be broadcast in mDNS records. 38 | .TP 39 | \fB\-v, \-\-version\fR 40 | Display version information. 41 | .TP 42 | \fB\-\-mdns-no-rsp\fR 43 | Don't announce RSP service via mDNS. 44 | .TP 45 | \fB\-\-mdns-no-daap\fR 46 | Don't announce DAAP service via mDNS. 47 | .TP 48 | \fB\-\-mdns-no-cname\fR 49 | Don't register owntone.local as CNAME via mDNS. 50 | .TP 51 | \fB\-\-mdns-no-web\fR 52 | Don't announce web interface via mDNS. 53 | .SH FILES 54 | .nf 55 | \fI/etc/owntone.conf\fR 56 | \fI/var/cache/owntone\fR 57 | .fi 58 | .SH SEE ALSO 59 | See \fIhttp://owntone.github.io/owntone-server/\fR for more in-depth information 60 | about using \fBOwnTone\fP. 61 | -------------------------------------------------------------------------------- /owntone.service.in: -------------------------------------------------------------------------------- 1 | # Note: Please keep this file in sync with owntone@.service.in 2 | 3 | [Unit] 4 | Description=DAAP/DACP (iTunes), RSP and MPD server, supports AirPlay and Remote 5 | Documentation=man:owntone(8) 6 | Requires=network.target local-fs.target avahi-daemon.socket 7 | After=network-online.target sound.target remote-fs.target pulseaudio.service 8 | 9 | [Service] 10 | ExecStart=@sbindir@/owntone -f 11 | 12 | # Constrain the upper limit of memory/swap that can be used; this prevents 13 | # the server from consuming all system memory (in event of bug/malformed user 14 | # curl/SMARTPL query etc) that would hang/freeze low resource and headless (ie 15 | # RPi) machines 16 | # 17 | # systemd will kill the process in such an event but would be auto-restarted as 18 | # per 'Restart' directive below 19 | # 20 | # Values derived from obersvations on rpi3 under load - limits are >50% above 21 | # seen high watermarks 22 | # 23 | # https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html 24 | MemoryMax=256M 25 | MemorySwapMax=32M 26 | 27 | # Restart, but set a limit so we don't restart indefinitely. Unfortunately, 28 | # systemd also applies the start limits to manual restarts, so that's why the 29 | # burst value allows for 10 restarts. 30 | Restart=on-failure 31 | RestartSec=5 32 | StartLimitBurst=10 33 | StartLimitInterval=600 34 | 35 | [Install] 36 | WantedBy=multi-user.target 37 | 38 | -------------------------------------------------------------------------------- /owntone@.service.in: -------------------------------------------------------------------------------- 1 | # Note: Please keep this file in sync with owntone.service.in 2 | 3 | [Unit] 4 | Description=DAAP/DACP (iTunes), RSP, MPD server, with AirPlay and Remote - %I 5 | Documentation=man:owntone(8) 6 | Requires=network.target local-fs.target avahi-daemon.socket 7 | After=network.target sound.target remote-fs.target pulseaudio.service 8 | 9 | [Service] 10 | ExecStart=@sbindir@/owntone -f -c /etc/owntone-%I.conf 11 | SyslogIdentifier=owntone-%I 12 | 13 | # Constrain the upper limit of memory/swap that can be used; this prevents 14 | # the server from consuming all system memory (in event of bug/malformed user 15 | # curl/SMARTPL query etc) that would hang/freeze low resource and headless (ie 16 | # RPi) machines 17 | # 18 | # systemd will kill the process in such an event but would be auto-restarted as 19 | # per 'Restart' directive below 20 | # 21 | # Values derived from obersvations on rpi3 under load - limits are >50% above 22 | # seen high watermarks 23 | # 24 | # https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html 25 | MemoryMax=256M 26 | MemorySwapMax=32M 27 | 28 | # Restart, but set a limit so we don't restart indefinitely. Unfortunately, 29 | # systemd also applies the start limits to manual restarts, so that's why the 30 | # burst value allows for 10 restarts. 31 | Restart=on-failure 32 | RestartSec=5 33 | StartLimitBurst=10 34 | StartLimitInterval=600 35 | 36 | [Install] 37 | WantedBy=multi-user.target 38 | -------------------------------------------------------------------------------- /scripts/freebsd_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # PROVIDE: owntone 4 | # REQUIRE: avahi_daemon dbus 5 | 6 | # Add the following lines to /etc/rc.conf to enable `owntone': 7 | # 8 | # owntone_enable="YES" 9 | # owntone_flags="" 10 | 11 | . /etc/rc.subr 12 | 13 | name="owntone" 14 | rcvar=`set_rcvar` 15 | 16 | command="/usr/local/sbin/owntone" 17 | command_args="-P /var/run/owntone.pid" 18 | pidfile="/var/run/owntone.pid" 19 | required_files="/usr/local/etc/owntone.conf" 20 | 21 | # read configuration and set defaults 22 | load_rc_config "$name" 23 | : ${owntone_enable="NO"} 24 | 25 | run_rc_command "$1" 26 | -------------------------------------------------------------------------------- /sqlext/Makefile.am: -------------------------------------------------------------------------------- 1 | pkglib_LTLIBRARIES = owntone-sqlext.la 2 | 3 | owntone_sqlext_la_SOURCES = sqlext.c 4 | owntone_sqlext_la_LDFLAGS = -avoid-version -module -shared 5 | owntone_sqlext_la_LIBADD = \ 6 | $(COMMON_LIBS) 7 | 8 | AM_CPPFLAGS += \ 9 | $(COMMON_CPPFLAGS) 10 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | owntone 2 | 3 | daap_query_hash.h 4 | dacp_prop_hash.h 5 | dmap_fields_hash.h 6 | -------------------------------------------------------------------------------- /src/artwork.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __ARTWORK_H__ 3 | #define __ARTWORK_H__ 4 | 5 | #define ART_FMT_PNG 1 6 | #define ART_FMT_JPEG 2 7 | #define ART_FMT_VP8 3 8 | 9 | #define ART_DEFAULT_HEIGHT 600 10 | #define ART_DEFAULT_WIDTH 600 11 | 12 | #include 13 | #include 14 | 15 | /* 16 | * Get the artwork image for an individual item (track) 17 | * 18 | * @out evbuf Event buffer that will contain the (scaled) image 19 | * @in id The mfi item id 20 | * @in max_w Requested maximum image width (may not be obeyed) 21 | * @in max_h Requested maximum image height (may not be obeyed) 22 | * @in format Requested format (may not be obeyed), 0 for default 23 | * @return ART_FMT_* on success, -1 on error or no artwork found 24 | */ 25 | int 26 | artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h, int format); 27 | 28 | /* 29 | * Get the artwork image for a group (an album or an artist) 30 | * 31 | * @out evbuf Event buffer that will contain the (scaled) image 32 | * @in id The group id (not the persistentid) 33 | * @in max_w Requested maximum image width (may not be obeyed) 34 | * @in max_h Requested maximum image height (may not be obeyed) 35 | * @in format Requested format (may not be obeyed), 0 for default 36 | * @return ART_FMT_* on success, -1 on error or no artwork found 37 | */ 38 | int 39 | artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h, int format); 40 | 41 | /* 42 | * Checks if the file is an artwork file (based on user config) 43 | * 44 | * @in filename Name of the file 45 | * @return true/false 46 | */ 47 | bool 48 | artwork_file_is_artwork(const char *filename); 49 | 50 | /* 51 | * Checks if the path (or URL) has file extension that is recognized as a 52 | * supported file type (e.g. ".jpg"). Also supports URL-encoded paths, e.g. 53 | * http://foo.com/bar.jpg?something 54 | * 55 | * @in path Path to the file (can also be a URL) 56 | * @return true/false 57 | */ 58 | bool 59 | artwork_extension_is_artwork(const char *path); 60 | 61 | #endif /* !__ARTWORK_H__ */ 62 | -------------------------------------------------------------------------------- /src/cache.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __CACHE_H__ 3 | #define __CACHE_H__ 4 | 5 | #include 6 | 7 | /* ----------------------------- DAAP cache API ---------------------------- */ 8 | 9 | void 10 | cache_daap_suspend(void); 11 | 12 | void 13 | cache_daap_resume(void); 14 | 15 | int 16 | cache_daap_get(struct evbuffer *evbuf, const char *query); 17 | 18 | void 19 | cache_daap_add(const char *query, const char *ua, int is_remote, int msec); 20 | 21 | int 22 | cache_daap_threshold_get(void); 23 | 24 | 25 | /* --------------------------- Transcode cache API ------------------------- */ 26 | 27 | int 28 | cache_xcode_header_get(struct evbuffer *evbuf, int *cached, uint32_t id, const char *format); 29 | 30 | int 31 | cache_xcode_toggle(bool enable); 32 | 33 | 34 | /* ---------------------------- Artwork cache API -------------------------- */ 35 | 36 | #define CACHE_ARTWORK_GROUP 0 37 | #define CACHE_ARTWORK_INDIVIDUAL 1 38 | 39 | void 40 | cache_artwork_ping(const char *path, time_t mtime, int del); 41 | 42 | int 43 | cache_artwork_delete_by_path(const char *path); 44 | 45 | int 46 | cache_artwork_purge_cruft(time_t ref); 47 | 48 | int 49 | cache_artwork_add(int type, int64_t persistentid, int max_w, int max_h, int format, char *filename, struct evbuffer *evbuf); 50 | 51 | int 52 | cache_artwork_get(int type, int64_t persistentid, int max_w, int max_h, int *cached, int *format, struct evbuffer *evbuf); 53 | 54 | int 55 | cache_artwork_stash(struct evbuffer *evbuf, const char *path, int format); 56 | 57 | int 58 | cache_artwork_read(struct evbuffer *evbuf, const char *path, int *format); 59 | 60 | /* ------------------------------- Cache API ------------------------------- */ 61 | 62 | int 63 | cache_init(void); 64 | 65 | void 66 | cache_deinit(void); 67 | 68 | #endif /* !__CACHE_H__ */ 69 | -------------------------------------------------------------------------------- /src/commands.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SRC_COMMANDS_H_ 3 | #define SRC_COMMANDS_H_ 4 | 5 | #include 6 | 7 | enum command_state { 8 | COMMAND_END = 0, 9 | COMMAND_PENDING = 1, 10 | }; 11 | 12 | /* 13 | * Function that will be executed in the event loop thread. 14 | * 15 | * If the function has pending events to complete, it needs to return 16 | * COMMAND_PENDING with 'ret' set to the number of pending events to wait for. 17 | * 18 | * If the function returns with COMMAND_END, command execution will proceed 19 | * with the "bottem half" function (if passed to the command_exec function) only 20 | * if 'ret' is 0. 21 | * 22 | * @param arg Opaque pointer passed by command_exec_sync or command_exec_async 23 | * @param ret Pointer to the return value for the caller of the command 24 | * @return COMMAND_END if there are no pending events (function execution is 25 | * complete) or COMMAND_PENDING if there are pending events 26 | */ 27 | typedef enum command_state (*command_function)(void *arg, int *ret); 28 | 29 | typedef void (*command_exit_cb)(void); 30 | 31 | 32 | struct commands_base; 33 | 34 | 35 | struct commands_base * 36 | commands_base_new(struct event_base *evbase, command_exit_cb exit_cb); 37 | 38 | int 39 | commands_base_free(struct commands_base *cmdbase); 40 | 41 | int 42 | commands_exec_returnvalue(struct commands_base *cmdbase); 43 | 44 | void 45 | commands_exec_end(struct commands_base *cmdbase, int retvalue); 46 | 47 | int 48 | commands_exec_sync(struct commands_base *cmdbase, command_function func, command_function func_bh, void *arg); 49 | 50 | int 51 | commands_exec_async(struct commands_base *cmdbase, command_function func, void *arg); 52 | 53 | void 54 | commands_base_destroy(struct commands_base *cmdbase); 55 | 56 | #endif /* SRC_COMMANDS_H_ */ 57 | -------------------------------------------------------------------------------- /src/conffile.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __CONFFILE_H__ 3 | #define __CONFFILE_H__ 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define CONFFILE CONFDIR "/owntone.conf" 11 | 12 | // Some shorthand macros for poor man's 13 | #define CFG_NAME_UNKNOWN_TITLE (cfg_getstr(cfg_getsec(cfg, "library"), "name_unknown_title")) 14 | #define CFG_NAME_UNKNOWN_ARTIST (cfg_getstr(cfg_getsec(cfg, "library"), "name_unknown_artist")) 15 | #define CFG_NAME_UNKNOWN_ALBUM (cfg_getstr(cfg_getsec(cfg, "library"), "name_unknown_album")) 16 | #define CFG_NAME_UNKNOWN_GENRE (cfg_getstr(cfg_getsec(cfg, "library"), "name_unknown_genre")) 17 | #define CFG_NAME_UNKNOWN_COMPOSER (cfg_getstr(cfg_getsec(cfg, "library"), "name_unknown_composer")) 18 | 19 | extern cfg_t *cfg; 20 | extern uint64_t libhash; 21 | extern uid_t runas_uid; 22 | extern gid_t runas_gid; 23 | 24 | int 25 | conffile_load(char *file); 26 | 27 | void 28 | conffile_unload(void); 29 | 30 | #endif /* !__CONFFILE_H__ */ 31 | -------------------------------------------------------------------------------- /src/daap_query.gperf: -------------------------------------------------------------------------------- 1 | %language=ANSI-C 2 | %readonly-tables 3 | %enum 4 | %switch=1 5 | %compare-lengths 6 | %define hash-function-name daap_query_field_hash 7 | %define lookup-function-name daap_query_field_lookup 8 | %define slot-name dmap_field 9 | %struct-type 10 | struct dmap_query_field_map { 11 | char *dmap_field; 12 | char *db_col; 13 | int as_int; 14 | }; 15 | %% 16 | "dmap.itemname", "f.title", 0 17 | "dmap.itemid", "f.id", 1 18 | "dmap.containeritemid", "f.id", 1 19 | "dmap.persistentid", "f.id", 1 20 | "daap.songalbum", "f.album", 0 21 | "daap.songalbumid", "f.songalbumid", 1 22 | "daap.songartist", "f.album_artist", 0 23 | "daap.songartistid", "f.songartistid", 1 24 | "daap.songalbumartist", "f.album_artist", 0 25 | "daap.songbitrate", "f.bitrate", 1 26 | "daap.songcomment", "f.comment", 0 27 | "daap.songcompilation", "f.compilation", 1 28 | "daap.songcomposer", "f.composer", 0 29 | "daap.songdatakind", "f.data_kind", 1 30 | "daap.songdataurl", "f.url", 0 31 | "daap.songdateadded", "f.time_added", 1 32 | "daap.songdatemodified", "f.time_modified", 1 33 | "daap.songlastskipdate", "f.time_skipped", 1 34 | "daap.songdatereleased", "f.date_released", 1 35 | "daap.songdescription", "f.description", 0 36 | "daap.songdisccount", "f.total_discs", 1 37 | "daap.songdiscnumber", "f.disc", 1 38 | "daap.songformat", "f.type", 0 39 | "daap.songgenre", "f.genre", 0 40 | "daap.songsamplerate", "f.samplerate", 1 41 | "daap.songsize", "f.file_size", 1 42 | "daap.songstoptime", "f.song_length", 1 43 | "daap.songtime", "f.song_length", 1 44 | "daap.songtrackcount", "f.total_tracks", 1 45 | "daap.songtracknumber", "f.track", 1 46 | "daap.songuserplaycount", "f.play_count", 1 47 | "daap.songuserskipcount", "f.skip_count", 1 48 | "daap.songyear", "f.year", 1 49 | "com.apple.itunes.mediakind", "f.media_kind", 1 50 | "com.apple.itunes.extended-media-kind", "f.media_kind", 1 51 | -------------------------------------------------------------------------------- /src/dacp_prop.gperf: -------------------------------------------------------------------------------- 1 | %language=ANSI-C 2 | %readonly-tables 3 | %enum 4 | %switch=1 5 | %compare-lengths 6 | %define hash-function-name dacp_hash_prop 7 | %define lookup-function-name dacp_find_prop 8 | %define slot-name desc 9 | %struct-type 10 | %omit-struct-type 11 | struct dacp_prop_map; 12 | %% 13 | "dmcp.volume", dacp_propget_volume, dacp_propset_volume 14 | "dmcp.device-volume", NULL, dacp_propset_devicevolume 15 | "dmcp.device-prevent-playback", NULL, dacp_propset_devicepreventplayback 16 | "dmcp.device-busy", NULL, dacp_propset_devicebusy 17 | "dacp.playerstate", dacp_propget_playerstate, NULL 18 | "dacp.nowplaying", dacp_propget_nowplaying, NULL 19 | "dacp.playingtime", dacp_propget_playingtime, dacp_propset_playingtime 20 | "dacp.volumecontrollable", dacp_propget_volumecontrollable, NULL 21 | "dacp.availableshufflestates", dacp_propget_availableshufflestates, NULL 22 | "dacp.availablerepeatstates", dacp_propget_availablerepeatstates, NULL 23 | "dacp.shufflestate", dacp_propget_shufflestate, dacp_propset_shufflestate 24 | "dacp.repeatstate", dacp_propget_repeatstate, dacp_propset_repeatstate 25 | "dacp.userrating", NULL, dacp_propset_userrating 26 | "dacp.fullscreenenabled", dacp_propget_fullscreenenabled, NULL 27 | "dacp.fullscreen", dacp_propget_fullscreen, NULL 28 | "dacp.visualizerenabled", dacp_propget_visualizerenabled, NULL 29 | "dacp.visualizer", dacp_propget_visualizer, NULL 30 | "com.apple.itunes.itms-songid", dacp_propget_itms_songid, NULL 31 | "com.apple.itunes.has-chapter-data", dacp_propget_haschapterdata, NULL 32 | "com.apple.itunes.mediakind", dacp_propget_mediakind, NULL 33 | "com.apple.itunes.extended-media-kind", dacp_propget_extendedmediakind, NULL 34 | -------------------------------------------------------------------------------- /src/db_init.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Christian Meffert 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #ifndef SRC_DB_INIT_H_ 20 | #define SRC_DB_INIT_H_ 21 | 22 | #include 23 | 24 | /* Rule of thumb: Will the current version of the server work with the new 25 | * version of the database? If yes, then it is a minor upgrade, if no, then it 26 | * is a major upgrade. In other words minor version upgrades permit downgrading 27 | * the server after the database was upgraded. */ 28 | #define SCHEMA_VERSION_MAJOR 22 29 | #define SCHEMA_VERSION_MINOR 2 30 | 31 | int 32 | db_init_indices(sqlite3 *hdl); 33 | 34 | int 35 | db_init_triggers(sqlite3 *hdl); 36 | 37 | int 38 | db_init_tables(sqlite3 *hdl); 39 | 40 | #endif /* SRC_DB_INIT_H_ */ 41 | -------------------------------------------------------------------------------- /src/db_upgrade.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Christian Meffert 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | #ifndef SRC_DB_UPGRADE_H_ 20 | #define SRC_DB_UPGRADE_H_ 21 | 22 | #include 23 | 24 | int 25 | db_upgrade(sqlite3 *hdl, int db_ver); 26 | 27 | #endif /* SRC_DB_UPGRADE_H_ */ 28 | -------------------------------------------------------------------------------- /src/evrtsp/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2000-2004 Niels Provos 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | #ifndef _LOG_H_ 28 | #define _LOG_H_ 29 | 30 | #ifdef __GNUC__ 31 | #define EV_CHECK_FMT(a,b) __attribute__((format(printf, a, b))) 32 | #else 33 | #define EV_CHECK_FMT(a,b) 34 | #endif 35 | 36 | void event_err(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3); 37 | void event_warn(const char *fmt, ...) EV_CHECK_FMT(1,2); 38 | void event_errx(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3); 39 | void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2); 40 | void event_msgx(const char *fmt, ...) EV_CHECK_FMT(1,2); 41 | void _event_debugx(const char *fmt, ...) EV_CHECK_FMT(1,2); 42 | 43 | #ifdef USE_DEBUG 44 | #define event_debug(x) _event_debugx x 45 | #else 46 | #define event_debug(x) do {;} while (0) 47 | #endif 48 | 49 | #undef EV_CHECK_FMT 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/evthr.h: -------------------------------------------------------------------------------- 1 | #ifndef __EVTHR_H__ 2 | #define __EVTHR_H__ 3 | 4 | enum evthr_res { 5 | EVTHR_RES_OK = 0, 6 | EVTHR_RES_BACKLOG, 7 | EVTHR_RES_RETRY, 8 | EVTHR_RES_NOCB, 9 | EVTHR_RES_FATAL 10 | }; 11 | 12 | struct evthr_pool; 13 | struct evthr; 14 | 15 | typedef void (*evthr_cb)(struct evthr *thr, void *cmd_arg, void *shared); 16 | typedef void (*evthr_init_cb)(struct evthr *thr, void *shared); 17 | typedef void (*evthr_exit_cb)(struct evthr *thr, void *shared); 18 | 19 | struct event_base * 20 | evthr_get_base(struct evthr *thr); 21 | 22 | void 23 | evthr_set_aux(struct evthr *thr, void *aux); 24 | 25 | void * 26 | evthr_get_aux(struct evthr *thr); 27 | 28 | enum evthr_res 29 | evthr_pool_defer(struct evthr_pool *pool, evthr_cb cb, void *arg); 30 | 31 | struct evthr_pool * 32 | evthr_pool_wexit_new(int nthreads, evthr_init_cb init_cb, evthr_exit_cb exit_cb, void *shared); 33 | 34 | void 35 | evthr_pool_free(struct evthr_pool *pool); 36 | 37 | enum evthr_res 38 | evthr_pool_stop(struct evthr_pool *pool); 39 | 40 | int 41 | evthr_pool_start(struct evthr_pool *pool); 42 | 43 | #endif /* !__EVTHR_H__ */ 44 | -------------------------------------------------------------------------------- /src/httpd.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __HTTPD_H__ 3 | #define __HTTPD_H__ 4 | 5 | #include 6 | 7 | /* 8 | * Gzips an evbuffer 9 | * 10 | * @in in Data to be compressed 11 | * @return Compressed data - must be freed by caller 12 | */ 13 | struct evbuffer * 14 | httpd_gzip_deflate(struct evbuffer *in); 15 | 16 | int 17 | httpd_init(const char *webroot); 18 | 19 | void 20 | httpd_deinit(void); 21 | 22 | #endif /* !__HTTPD_H__ */ 23 | -------------------------------------------------------------------------------- /src/httpd_daap.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __HTTPD_DAAP_H__ 3 | #define __HTTPD_DAAP_H__ 4 | 5 | int 6 | daap_session_is_valid(int id); 7 | 8 | struct evbuffer * 9 | daap_reply_build(const char *uri, const char *user_agent, int is_remote); 10 | 11 | #endif /* !__HTTPD_DAAP_H__ */ 12 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | Makefile.in 4 | Makefile 5 | *.o 6 | *.lo 7 | *.a 8 | *.la 9 | .dirstamp 10 | .deps/ 11 | .libs/ 12 | 13 | # autofoo stuff 14 | autom4te.cache 15 | aclocal.m4 16 | compile 17 | config.guess 18 | config.h 19 | config.h.in 20 | config.log 21 | config.status 22 | config.sub 23 | configure 24 | depcomp 25 | install-sh 26 | libtool 27 | ltmain.sh 28 | missing 29 | stamp-h1 30 | autotools-stamp 31 | build-stamp 32 | ar-lib 33 | 34 | /.settings 35 | /.cproject 36 | /.project 37 | /.autotools 38 | /.vscode 39 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = tests 2 | 3 | noinst_LIBRARIES = librespot-c.a 4 | 5 | SHANNON_SRC = \ 6 | src/shannon/ShannonFast.c src/shannon/Shannon.h src/shannon/ShannonInternal.h 7 | 8 | PROTO_SRC = \ 9 | src/proto/keyexchange.pb-c.c src/proto/keyexchange.pb-c.h \ 10 | src/proto/authentication.pb-c.c src/proto/authentication.pb-c.h \ 11 | src/proto/mercury.pb-c.c src/proto/mercury.pb-c.h \ 12 | src/proto/metadata.pb-c.c src/proto/metadata.pb-c.h 13 | 14 | HTTP_PROTO_SRC = \ 15 | src/proto/connectivity.pb-c.c src/proto/connectivity.pb-c.h \ 16 | src/proto/clienttoken.pb-c.c src/proto/clienttoken.pb-c.h \ 17 | src/proto/login5_user_info.pb-c.h src/proto/login5_user_info.pb-c.c \ 18 | src/proto/login5.pb-c.h src/proto/login5.pb-c.c \ 19 | src/proto/login5_identifiers.pb-c.h src/proto/login5_identifiers.pb-c.c \ 20 | src/proto/login5_credentials.pb-c.h src/proto/login5_credentials.pb-c.c \ 21 | src/proto/login5_client_info.pb-c.h src/proto/login5_client_info.pb-c.c \ 22 | src/proto/login5_challenges_hashcash.pb-c.h src/proto/login5_challenges_hashcash.pb-c.c \ 23 | src/proto/login5_challenges_code.pb-c.h src/proto/login5_challenges_code.pb-c.c \ 24 | src/proto/google_duration.pb-c.h src/proto/google_duration.pb-c.c \ 25 | src/proto/storage_resolve.pb-c.h src/proto/storage_resolve.pb-c.c 26 | 27 | CORE_SRC = \ 28 | src/librespot-c.c src/connection.c src/channel.c src/crypto.c src/commands.c \ 29 | src/http.c 30 | 31 | librespot_c_a_SOURCES = \ 32 | $(CORE_SRC) \ 33 | $(SHANNON_SRC) \ 34 | $(PROTO_SRC) \ 35 | $(HTTP_PROTO_SRC) 36 | 37 | noinst_HEADERS = \ 38 | librespot-c.h src/librespot-c-internal.h src/connection.h \ 39 | src/channel.h src/crypto.h src/commands.h src/http.h 40 | 41 | EXTRA_DIST = README.md LICENSE 42 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/README.md: -------------------------------------------------------------------------------- 1 | Limited C version of librespot. No Spotify Connect support. Library only. Used 2 | by [OwnTone](https://github.com/owntone/owntone-server). 3 | 4 | Build: 5 | - autoreconf -i && ./configure && make 6 | 7 | Test: 8 | - make check 9 | - ./tests/test1 10 | 11 | Dependencies: 12 | - libevent-dev libgcrypt20-dev libcurl4-gnutls-dev libjson-c-dev libprotobuf-c-dev 13 | 14 | Credits: 15 | - librespot (https://github.com/librespot-org/librespot) 16 | - timniederhausen for Shannon cipher (https://github.com/timniederhausen/shannon) 17 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([librespot-c], [0.1]) 2 | AC_CONFIG_AUX_DIR([.]) 3 | AM_INIT_AUTOMAKE([foreign subdir-objects]) 4 | AM_SILENT_RULES([yes]) 5 | 6 | dnl Defines _GNU_SOURCE globally when needed 7 | AC_USE_SYSTEM_EXTENSIONS 8 | 9 | AC_PROG_CC 10 | AM_PROG_AR 11 | AC_PROG_RANLIB 12 | 13 | AM_CPPFLAGS="-Wall" 14 | AC_SUBST([AM_CPPFLAGS]) 15 | 16 | AC_CHECK_HEADERS_ONCE([sys/utsname.h]) 17 | 18 | AC_CHECK_HEADERS([endian.h sys/endian.h libkern/OSByteOrder.h], [found_endian_headers=yes; break;]) 19 | AS_IF([test "x$found_endian_headers" != "xyes"], [AC_MSG_ERROR([[Missing functions to swap byte order]])]) 20 | 21 | AC_SEARCH_LIBS([pthread_exit], [pthread], [], [AC_MSG_ERROR([[pthreads library is required]])]) 22 | 23 | PKG_CHECK_MODULES([LIBEVENT], [libevent]) 24 | PKG_CHECK_MODULES([JSON_C], [json-c]) 25 | PKG_CHECK_MODULES([LIBGCRYPT], [libgcrypt], [], [ 26 | AM_PATH_LIBGCRYPT([], [], [AC_MSG_ERROR([[libgcrypt is required]])]) 27 | ]) 28 | 29 | dnl Need 7.83.0 for curl_easy_nextheader() 30 | PKG_CHECK_MODULES([LIBCURL], [libcurl >= 7.83.0]) 31 | PKG_CHECK_MODULES([LIBPROTOBUF_C], [libprotobuf-c]) 32 | 33 | AC_CONFIG_FILES([Makefile tests/Makefile]) 34 | AC_OUTPUT 35 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/channel.h: -------------------------------------------------------------------------------- 1 | struct sp_channel * 2 | channel_get(uint32_t channel_id, struct sp_session *session); 3 | 4 | void 5 | channel_free(struct sp_channel *channel); 6 | 7 | void 8 | channel_free_all(struct sp_session *session); 9 | 10 | int 11 | channel_new(struct sp_channel **channel, struct sp_session *session, const char *path, struct event_base *evbase, event_callback_fn write_cb); 12 | 13 | int 14 | channel_data_write(struct sp_channel *channel); 15 | 16 | void 17 | channel_play(struct sp_channel *channel); 18 | 19 | void 20 | channel_stop(struct sp_channel *channel); 21 | 22 | int 23 | channel_seek(struct sp_channel *channel, size_t pos); 24 | 25 | void 26 | channel_pause(struct sp_channel *channel); 27 | 28 | void 29 | channel_retry(struct sp_channel *channel); 30 | 31 | int 32 | channel_msg_read(uint16_t *channel_id, uint8_t *msg, size_t msg_len, struct sp_session *session); 33 | 34 | int 35 | channel_http_body_read(struct sp_channel *channel, uint8_t *body, size_t body_len); 36 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/commands.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SRC_COMMANDS_H_ 3 | #define SRC_COMMANDS_H_ 4 | 5 | #include 6 | 7 | enum command_state { 8 | COMMAND_END = 0, 9 | COMMAND_PENDING = 1, 10 | }; 11 | 12 | /* 13 | * Function that will be executed in the event loop thread. 14 | * 15 | * If the function has pending events to complete, it needs to return 16 | * COMMAND_PENDING with 'ret' set to the number of pending events to wait for. 17 | * 18 | * If the function returns with COMMAND_END, command execution will proceed 19 | * with the "bottem half" function (if passed to the command_exec function) only 20 | * if 'ret' is 0. 21 | * 22 | * @param arg Opaque pointer passed by command_exec_sync or command_exec_async 23 | * @param ret Pointer to the return value for the caller of the command 24 | * @return COMMAND_END if there are no pending events (function execution is 25 | * complete) or COMMAND_PENDING if there are pending events 26 | */ 27 | typedef enum command_state (*command_function)(void *arg, int *ret); 28 | 29 | typedef void (*command_exit_cb)(void); 30 | 31 | 32 | struct commands_base; 33 | 34 | 35 | struct commands_base * 36 | commands_base_new(struct event_base *evbase, command_exit_cb exit_cb); 37 | 38 | int 39 | commands_base_free(struct commands_base *cmdbase); 40 | 41 | int 42 | commands_exec_returnvalue(struct commands_base *cmdbase); 43 | 44 | void 45 | commands_exec_end(struct commands_base *cmdbase, int retvalue); 46 | 47 | int 48 | commands_exec_sync(struct commands_base *cmdbase, command_function func, command_function func_bh, void *arg); 49 | 50 | int 51 | commands_exec_async(struct commands_base *cmdbase, command_function func, void *arg); 52 | 53 | void 54 | commands_base_destroy(struct commands_base *cmdbase); 55 | 56 | #endif /* SRC_COMMANDS_H_ */ 57 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/connection.h: -------------------------------------------------------------------------------- 1 | void 2 | ap_disconnect(struct sp_connection *conn); 3 | 4 | enum sp_error 5 | ap_connect(struct sp_connection *conn, struct sp_server *server, time_t *cooldown_ts, struct sp_conn_callbacks *cb, void *cb_arg); 6 | 7 | void 8 | ap_blacklist(struct sp_server *server); 9 | 10 | int 11 | seq_requests_check(void); 12 | 13 | struct sp_seq_request * 14 | seq_request_get(enum sp_seq_type seq_type, int n, bool use_legacy); 15 | 16 | void 17 | seq_next_set(struct sp_session *session, enum sp_seq_type seq_type); 18 | 19 | enum sp_error 20 | seq_request_prepare(struct sp_seq_request *request, struct sp_conn_callbacks *cb, struct sp_session *session); 21 | 22 | enum sp_error 23 | msg_tcp_read_one(struct sp_tcp_message *tmsg, struct sp_connection *conn); 24 | 25 | enum sp_error 26 | msg_handle(struct sp_message *msg, struct sp_session *session); 27 | 28 | void 29 | msg_clear(struct sp_message *msg); 30 | 31 | int 32 | msg_make(struct sp_message *msg, struct sp_seq_request *req, struct sp_session *session); 33 | 34 | enum sp_error 35 | msg_tcp_send(struct sp_tcp_message *tmsg, struct sp_connection *conn); 36 | 37 | enum sp_error 38 | msg_http_send(struct http_response *hres, struct http_request *hreq, struct http_session *hses); 39 | 40 | enum sp_error 41 | msg_pong(struct sp_session *session); 42 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/connectivity.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spotify.clienttoken.data.v0; 4 | 5 | message ConnectivitySdkData { 6 | PlatformSpecificData platform_specific_data = 1; 7 | string device_id = 2; 8 | } 9 | 10 | message PlatformSpecificData { 11 | oneof data { 12 | NativeAndroidData android = 1; 13 | NativeIOSData ios = 2; 14 | NativeDesktopMacOSData desktop_macos = 3; 15 | NativeDesktopWindowsData desktop_windows = 4; 16 | NativeDesktopLinuxData desktop_linux = 5; 17 | } 18 | } 19 | 20 | message NativeAndroidData { 21 | Screen screen_dimensions = 1; 22 | string android_version = 2; 23 | int32 api_version = 3; 24 | string device_name = 4; 25 | string model_str = 5; 26 | string vendor = 6; 27 | string vendor_2 = 7; 28 | int32 unknown_value_8 = 8; 29 | } 30 | 31 | message NativeIOSData { 32 | // https://developer.apple.com/documentation/uikit/uiuserinterfaceidiom 33 | int32 user_interface_idiom = 1; 34 | bool target_iphone_simulator = 2; 35 | string hw_machine = 3; 36 | string system_version = 4; 37 | string simulator_model_identifier = 5; 38 | } 39 | 40 | message NativeDesktopWindowsData { 41 | int32 os_version = 1; 42 | int32 os_build = 3; 43 | // https://docs.microsoft.com/en-us/dotnet/api/system.platformid?view=net-6.0 44 | int32 platform_id = 4; 45 | int32 unknown_value_5 = 5; 46 | int32 unknown_value_6 = 6; 47 | // https://docs.microsoft.com/en-us/dotnet/api/system.reflection.imagefilemachine?view=net-6.0 48 | int32 image_file_machine = 7; 49 | // https://docs.microsoft.com/en-us/dotnet/api/system.reflection.portableexecutable.machine?view=net-6.0 50 | int32 pe_machine = 8; 51 | bool unknown_value_10 = 10; 52 | } 53 | 54 | message NativeDesktopLinuxData { 55 | string system_name = 1; // uname -s 56 | string system_release = 2; // -r 57 | string system_version = 3; // -v 58 | string hardware = 4; // -i 59 | } 60 | 61 | message NativeDesktopMacOSData { 62 | string system_version = 1; 63 | string hw_model = 2; 64 | string compiled_cpu_type = 3; 65 | } 66 | 67 | message Screen { 68 | int32 width = 1; 69 | int32 height = 2; 70 | int32 density = 3; 71 | int32 unknown_value_4 = 4; 72 | int32 unknown_value_5 = 5; 73 | } 74 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/google_duration.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package google.protobuf; 4 | 5 | message Duration { 6 | int64 seconds = 1; 7 | int32 nanos = 2; 8 | } 9 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/login5.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spotify.login5.v3; 4 | 5 | import "login5_client_info.proto"; 6 | import "login5_user_info.proto"; 7 | import "login5_challenges_code.proto"; 8 | import "login5_challenges_hashcash.proto"; 9 | import "login5_credentials.proto"; 10 | import "login5_identifiers.proto"; 11 | 12 | message Challenges { 13 | repeated Challenge challenges = 1; 14 | } 15 | 16 | message Challenge { 17 | oneof challenge { 18 | challenges.HashcashChallenge hashcash = 1; 19 | challenges.CodeChallenge code = 2; 20 | } 21 | } 22 | 23 | message ChallengeSolutions { 24 | repeated ChallengeSolution solutions = 1; 25 | } 26 | 27 | message ChallengeSolution { 28 | oneof solution { 29 | challenges.HashcashSolution hashcash = 1; 30 | challenges.CodeSolution code = 2; 31 | } 32 | } 33 | 34 | message LoginRequest { 35 | ClientInfo client_info = 1; 36 | bytes login_context = 2; 37 | ChallengeSolutions challenge_solutions = 3; 38 | 39 | oneof login_method { 40 | credentials.StoredCredential stored_credential = 100; 41 | credentials.Password password = 101; 42 | credentials.FacebookAccessToken facebook_access_token = 102; 43 | identifiers.PhoneNumber phone_number = 103; 44 | credentials.OneTimeToken one_time_token = 104; 45 | credentials.ParentChildCredential parent_child_credential = 105; 46 | credentials.AppleSignInCredential apple_sign_in_credential = 106; 47 | credentials.SamsungSignInCredential samsung_sign_in_credential = 107; 48 | credentials.GoogleSignInCredential google_sign_in_credential = 108; 49 | } 50 | } 51 | 52 | message LoginOk { 53 | string username = 1; 54 | string access_token = 2; 55 | bytes stored_credential = 3; 56 | int32 access_token_expires_in = 4; 57 | } 58 | 59 | message LoginResponse { 60 | repeated Warnings warnings = 4; 61 | enum Warnings { 62 | UNKNOWN_WARNING = 0; 63 | DEPRECATED_PROTOCOL_VERSION = 1; 64 | } 65 | 66 | bytes login_context = 5; 67 | string identifier_token = 6; 68 | UserInfo user_info = 7; 69 | 70 | oneof response { 71 | LoginOk ok = 1; 72 | LoginError error = 2; 73 | Challenges challenges = 3; 74 | } 75 | } 76 | 77 | enum LoginError { 78 | UNKNOWN_ERROR = 0; 79 | INVALID_CREDENTIALS = 1; 80 | BAD_REQUEST = 2; 81 | UNSUPPORTED_LOGIN_PROTOCOL = 3; 82 | TIMEOUT = 4; 83 | UNKNOWN_IDENTIFIER = 5; 84 | TOO_MANY_ATTEMPTS = 6; 85 | INVALID_PHONENUMBER = 7; 86 | TRY_AGAIN_LATER = 8; 87 | } 88 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/login5_challenges_code.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spotify.login5.v3.challenges; 4 | 5 | message CodeChallenge { 6 | Method method = 1; 7 | enum Method { 8 | UNKNOWN = 0; 9 | SMS = 1; 10 | } 11 | 12 | int32 code_length = 2; 13 | int32 expires_in = 3; 14 | string canonical_phone_number = 4; 15 | } 16 | 17 | message CodeSolution { 18 | string code = 1; 19 | } 20 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/login5_challenges_hashcash.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spotify.login5.v3.challenges; 4 | 5 | import "google_duration.proto"; 6 | 7 | message HashcashChallenge { 8 | bytes prefix = 1; 9 | int32 length = 2; 10 | } 11 | 12 | message HashcashSolution { 13 | bytes suffix = 1; 14 | google.protobuf.Duration duration = 2; 15 | } 16 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/login5_client_info.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spotify.login5.v3; 4 | 5 | message ClientInfo { 6 | string client_id = 1; 7 | string device_id = 2; 8 | } 9 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/login5_credentials.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spotify.login5.v3.credentials; 4 | 5 | message StoredCredential { 6 | string username = 1; 7 | bytes data = 2; 8 | } 9 | 10 | message Password { 11 | string id = 1; 12 | string password = 2; 13 | bytes padding = 3; 14 | } 15 | 16 | message FacebookAccessToken { 17 | string fb_uid = 1; 18 | string access_token = 2; 19 | } 20 | 21 | message OneTimeToken { 22 | string token = 1; 23 | } 24 | 25 | message ParentChildCredential { 26 | string child_id = 1; 27 | StoredCredential parent_stored_credential = 2; 28 | } 29 | 30 | message AppleSignInCredential { 31 | string auth_code = 1; 32 | string redirect_uri = 2; 33 | string bundle_id = 3; 34 | } 35 | 36 | message SamsungSignInCredential { 37 | string auth_code = 1; 38 | string redirect_uri = 2; 39 | string id_token = 3; 40 | string token_endpoint_url = 4; 41 | } 42 | 43 | message GoogleSignInCredential { 44 | string auth_code = 1; 45 | string redirect_uri = 2; 46 | } 47 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/login5_identifiers.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spotify.login5.v3.identifiers; 4 | 5 | message PhoneNumber { 6 | string number = 1; 7 | string iso_country_code = 2; 8 | string country_calling_code = 3; 9 | } 10 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/login5_user_info.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spotify.login5.v3; 4 | 5 | message UserInfo { 6 | string name = 1; 7 | string email = 2; 8 | bool email_verified = 3; 9 | string birthdate = 4; 10 | 11 | Gender gender = 5; 12 | enum Gender { 13 | UNKNOWN = 0; 14 | MALE = 1; 15 | FEMALE = 2; 16 | NEUTRAL = 3; 17 | } 18 | 19 | string phone_number = 6; 20 | bool phone_number_verified = 7; 21 | bool email_already_registered = 8; 22 | } 23 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/mercury.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | message MercuryMultiGetRequest { 4 | repeated MercuryRequest request = 0x1; 5 | } 6 | 7 | message MercuryMultiGetReply { 8 | repeated MercuryReply reply = 0x1; 9 | } 10 | 11 | message MercuryRequest { 12 | optional string uri = 0x1; 13 | optional string content_type = 0x2; 14 | optional bytes body = 0x3; 15 | optional bytes etag = 0x4; 16 | } 17 | 18 | message MercuryReply { 19 | optional sint32 status_code = 0x1; 20 | optional string status_message = 0x2; 21 | optional CachePolicy cache_policy = 0x3; 22 | enum CachePolicy { 23 | CACHE_NO = 0x1; 24 | CACHE_PRIVATE = 0x2; 25 | CACHE_PUBLIC = 0x3; 26 | } 27 | optional sint32 ttl = 0x4; 28 | optional bytes etag = 0x5; 29 | optional string content_type = 0x6; 30 | optional bytes body = 0x7; 31 | } 32 | 33 | 34 | message Header { 35 | optional string uri = 0x01; 36 | optional string content_type = 0x02; 37 | optional string method = 0x03; 38 | optional sint32 status_code = 0x04; 39 | repeated UserField user_fields = 0x06; 40 | } 41 | 42 | message UserField { 43 | optional string key = 0x01; 44 | optional bytes value = 0x02; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/proto/storage_resolve.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package spotify.download.proto; 4 | 5 | message StorageResolveResponse { 6 | Result result = 1; 7 | enum Result { 8 | CDN = 0; 9 | STORAGE = 1; 10 | RESTRICTED = 3; 11 | } 12 | 13 | repeated string cdnurl = 2; 14 | bytes fileid = 4; 15 | } 16 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/shannon/Shannon.h: -------------------------------------------------------------------------------- 1 | /* $Id: $ */ 2 | /* Shannon: Shannon stream cipher and MAC header files */ 3 | 4 | /* 5 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED 6 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 7 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND AGAINST 8 | INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR 9 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 10 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 11 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 12 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 13 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 14 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 15 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 | */ 17 | 18 | #ifndef _SHN_DEFINED 19 | #define _SHN_DEFINED 1 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | #include 26 | 27 | #define SHANNON_N 16 28 | 29 | typedef struct { 30 | uint32_t R[SHANNON_N]; /* Working storage for the shift register */ 31 | uint32_t CRC[SHANNON_N]; /* Working storage for CRC accumulation */ 32 | uint32_t initR[SHANNON_N]; /* saved register contents */ 33 | uint32_t konst; /* key dependent semi-constant */ 34 | uint32_t sbuf; /* encryption buffer */ 35 | uint32_t mbuf; /* partial word MAC buffer */ 36 | int nbuf; /* number of part-word stream bits buffered */ 37 | } shn_ctx; 38 | 39 | /* interface definitions */ 40 | void shn_key(shn_ctx *c, const uint8_t key[], int keylen); /* set key */ 41 | void shn_nonce(shn_ctx *c, const uint8_t nonce[], int nlen); /* set Init Vector */ 42 | void shn_stream(shn_ctx *c, uint8_t *buf, int nbytes); /* stream cipher */ 43 | void shn_maconly(shn_ctx *c, uint8_t *buf, int nbytes); /* accumulate MAC */ 44 | void shn_encrypt(shn_ctx *c, uint8_t *buf, int nbytes); /* encrypt + MAC */ 45 | void shn_decrypt(shn_ctx *c, uint8_t *buf, int nbytes); /* decrypt + MAC */ 46 | void shn_finish(shn_ctx *c, uint8_t *buf, int nbytes); /* finalise MAC */ 47 | 48 | #ifdef __cplusplus 49 | } 50 | #endif 51 | 52 | #endif /* _SHN_DEFINED */ 53 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/src/shannon/ShannonInternal.h: -------------------------------------------------------------------------------- 1 | #ifndef SHANNONINTERNAL_H 2 | #define SHANNONINTERNAL_H 3 | 4 | #include "Shannon.h" 5 | 6 | #include 7 | 8 | #define N SHANNON_N 9 | #define WORDSIZE 32 10 | #define UCHAR unsigned char 11 | 12 | #define WORD uint32_t 13 | #define WORD_MAX UINT32_MAX 14 | 15 | #if WORD_MAX == 0xffffffff 16 | #define ROTL(w,x) (((w) << (x))|((w) >> (32 - (x)))) 17 | #define ROTR(w,x) (((w) >> (x))|((w) << (32 - (x)))) 18 | #else 19 | #define ROTL(w,x) (((w) << (x))|(((w) & 0xffffffff) >> (32 - (x)))) 20 | #define ROTR(w,x) ((((w) & 0xffffffff) >> (x))|((w) << (32 - (x)))) 21 | #endif 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/tests/.gitignore: -------------------------------------------------------------------------------- 1 | test1 2 | test2 3 | -------------------------------------------------------------------------------- /src/inputs/librespot-c/tests/Makefile.am: -------------------------------------------------------------------------------- 1 | TEST_CFLAGS = $(CFLAGS) $(JSON_C_CFLAGS) $(LIBCURL_CFLAGS) $(LIBEVENT_CFLAGS) $(LIBGCRYPT_CFLAGS) $(LIBPROTOBUF_C_CFLAGS) 2 | TEST_LIBS = $(LIBS) $(JSON_C_LIBS) $(LIBCURL_LIBS) $(LIBEVENT_LIBS) $(LIBGCRYPT_LIBS) $(LIBPROTOBUF_C_LIBS) 3 | 4 | AM_CPPFLAGS = -I$(top_srcdir) 5 | 6 | test1_SOURCES = test1.c 7 | test1_LDADD = $(top_builddir)/librespot-c.a -lpthread $(TEST_LIBS) 8 | test1_CFLAGS = $(TEST_CFLAGS) 9 | 10 | test2_SOURCES = test2.c 11 | test2_LDADD = $(top_builddir)/librespot-c.a -lpthread $(TEST_LIBS) 12 | test2_CFLAGS = $(TEST_CFLAGS) 13 | 14 | check_PROGRAMS = test1 test2 15 | -------------------------------------------------------------------------------- /src/inputs/spotify.h: -------------------------------------------------------------------------------- 1 | #ifndef __SPOTIFY_H__ 2 | #define __SPOTIFY_H__ 3 | 4 | #include 5 | #include 6 | 7 | struct spotify_status 8 | { 9 | bool installed; 10 | bool logged_in; 11 | char username[128]; 12 | bool has_podcast_support; 13 | }; 14 | 15 | struct spotify_backend 16 | { 17 | int (*init)(void); 18 | void (*deinit)(void); 19 | int (*login)(const char *username, const char *token, const char **errmsg); 20 | void (*logout)(void); 21 | int (*relogin)(void); 22 | void (*uri_register)(const char *uri); 23 | void (*status_get)(struct spotify_status *status); 24 | }; 25 | 26 | int 27 | spotify_init(void); 28 | 29 | void 30 | spotify_deinit(void); 31 | 32 | int 33 | spotify_login(const char *username, const char *token, const char **errmsg); 34 | 35 | void 36 | spotify_logout(void); 37 | 38 | int 39 | spotify_relogin(void); 40 | 41 | void 42 | spotify_uri_register(const char *uri); 43 | 44 | void 45 | spotify_status_get(struct spotify_status *status); 46 | 47 | #endif /* !__SPOTIFY_H__ */ 48 | -------------------------------------------------------------------------------- /src/lastfm.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __LASTFM_H__ 3 | #define __LASTFM_H__ 4 | 5 | #include 6 | 7 | int 8 | lastfm_login_user(const char *user, const char *password, char **errmsg); 9 | 10 | void 11 | lastfm_login(char **arglist); 12 | 13 | void 14 | lastfm_logout(void); 15 | 16 | int 17 | lastfm_scrobble(int id); 18 | 19 | bool 20 | lastfm_is_enabled(void); 21 | 22 | int 23 | lastfm_init(void); 24 | 25 | #endif /* !__LASTFM_H__ */ 26 | -------------------------------------------------------------------------------- /src/library/spotify_webapi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Espen Jürgensen 3 | * Copyright (C) 2016 Christian Meffert 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef SRC_SPOTIFY_WEBAPI_H_ 21 | #define SRC_SPOTIFY_WEBAPI_H_ 22 | 23 | #include 24 | #include 25 | 26 | #include "http.h" 27 | 28 | 29 | struct spotifywebapi_status_info 30 | { 31 | bool token_valid; 32 | char user[100]; 33 | char country[3]; // ISO 3166-1 alpha-2 country code 34 | char granted_scope[250]; 35 | char required_scope[250]; 36 | }; 37 | 38 | struct spotifywebapi_access_token 39 | { 40 | int expires_in; 41 | char *token; 42 | }; 43 | 44 | 45 | char * 46 | spotifywebapi_oauth_uri_get(void); 47 | int 48 | spotifywebapi_oauth_callback(struct evkeyvalq *param, const char **errmsg); 49 | 50 | void 51 | spotifywebapi_fullrescan(void); 52 | void 53 | spotifywebapi_rescan(void); 54 | void 55 | spotifywebapi_purge(void); 56 | char * 57 | spotifywebapi_artwork_url_get(const char *uri, int max_w, int max_h); 58 | 59 | void 60 | spotifywebapi_status_info_get(struct spotifywebapi_status_info *info); 61 | void 62 | spotifywebapi_access_token_get(struct spotifywebapi_access_token *info); 63 | 64 | #endif /* SRC_SPOTIFY_WEBAPI_H_ */ 65 | -------------------------------------------------------------------------------- /src/listenbrainz.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __LISTENBRAINZ_H__ 3 | #define __LISTENBRAINZ_H__ 4 | 5 | struct listenbrainz_status { 6 | bool disabled; 7 | char *user_name; 8 | bool token_valid; 9 | char *message; 10 | }; 11 | 12 | int 13 | listenbrainz_scrobble(int mfi_id); 14 | int 15 | listenbrainz_token_set(const char *token); 16 | int 17 | listenbrainz_token_delete(void); 18 | int 19 | listenbrainz_status_get(struct listenbrainz_status *status); 20 | void 21 | listenbrainz_status_free(struct listenbrainz_status *status, bool content_only); 22 | int 23 | listenbrainz_init(void); 24 | 25 | #endif /* !__LISTENBRAINZ_H__ */ 26 | -------------------------------------------------------------------------------- /src/listener.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __LISTENER_H__ 3 | #define __LISTENER_H__ 4 | 5 | enum listener_event_type 6 | { 7 | /* The player has been started, stopped or seeked */ 8 | LISTENER_PLAYER = (1 << 0), 9 | /* The current playback queue has been modified */ 10 | LISTENER_QUEUE = (1 << 1), 11 | /* The volume has been changed */ 12 | LISTENER_VOLUME = (1 << 2), 13 | /* Speaker status changes (enabled/disabled or verification status) */ 14 | LISTENER_SPEAKER = (1 << 3), 15 | /* Options like repeat, random has been changed */ 16 | LISTENER_OPTIONS = (1 << 4), 17 | /* The library has been modified */ 18 | LISTENER_DATABASE = (1 << 5), 19 | /* A stored playlist has been modified (create, delete, add, rename) */ 20 | LISTENER_STORED_PLAYLIST = (1 << 6), 21 | /* A library update has started or finished */ 22 | LISTENER_UPDATE = (1 << 7), 23 | /* A pairing request has started or finished */ 24 | LISTENER_PAIRING = (1 << 8), 25 | /* Spotify status changes (login, logout) */ 26 | LISTENER_SPOTIFY = (1 << 9), 27 | /* Last.fm status changes (enable/disable scrobbling) */ 28 | LISTENER_LASTFM = (1 << 10), 29 | /* Song rating changes */ 30 | LISTENER_RATING = (1 << 11), 31 | }; 32 | 33 | typedef void (*notify)(short event_mask, void *ctx); 34 | 35 | /* 36 | * Registers the given callback function to the given event types. 37 | * This function is not thread safe. Listeners must be added once at startup. 38 | * 39 | * @param notify_cb Callback function (should be a non-blocking function, 40 | * especially when the event is from the player) 41 | * @param event_mask Event mask, one or more of LISTENER_* 42 | * @param ctx Context will be passed to the notify callback 43 | * @return 0 on success, -1 on failure 44 | */ 45 | int 46 | listener_add(notify notify_cb, short event_mask, void *ctx); 47 | 48 | /* 49 | * Removes the given callback function 50 | * This function is not thread safe. Listeners must be removed once at shutdown. 51 | * 52 | * @param notify_cb Callback function 53 | * @return 0 on success, -1 if the callback was not registered 54 | */ 55 | int 56 | listener_remove(notify notify_cb); 57 | 58 | /* 59 | * Calls the callback function of the registered listeners listening for the 60 | * given type of event. 61 | * 62 | * @param event_mask Event mask, one or more of LISTENER_* 63 | * 64 | */ 65 | void 66 | listener_notify(short event_mask); 67 | 68 | #endif /* !__LISTENER_H__ */ 69 | -------------------------------------------------------------------------------- /src/logger.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __LOGGER_H__ 3 | #define __LOGGER_H__ 4 | 5 | #include 6 | 7 | /* Log domains */ 8 | #define L_CONF 0 9 | #define L_DAAP 1 10 | #define L_DB 2 11 | #define L_HTTPD 3 12 | #define L_HTTP 4 13 | #define L_MAIN 5 14 | #define L_MDNS 6 15 | #define L_MISC 7 16 | #define L_RSP 8 17 | #define L_SCAN 9 18 | #define L_XCODE 10 19 | /* libevent logging */ 20 | #define L_EVENT 11 21 | #define L_REMOTE 12 22 | #define L_DACP 13 23 | #define L_FFMPEG 14 24 | #define L_ART 15 25 | #define L_PLAYER 16 26 | #define L_RAOP 17 27 | #define L_LAUDIO 18 28 | #define L_DMAP 19 29 | #define L_DBPERF 20 30 | #define L_SPOTIFY 21 31 | #define L_SCROBBLE 22 32 | #define L_CACHE 23 33 | #define L_MPD 24 34 | #define L_STREAMING 25 35 | #define L_CAST 26 36 | #define L_FIFO 27 37 | #define L_LIB 28 38 | #define L_WEB 29 39 | #define L_AIRPLAY 30 40 | #define L_RCP 31 41 | 42 | #define N_LOGDOMAINS 32 43 | 44 | /* Severities */ 45 | #define E_FATAL 0 46 | #define E_LOG 1 47 | #define E_WARN 2 48 | #define E_INFO 3 49 | #define E_DBG 4 50 | #define E_SPAM 5 51 | 52 | 53 | 54 | void 55 | DPRINTF(int severity, int domain, const char *fmt, ...) __attribute__((format(printf, 3, 4))); 56 | 57 | void 58 | DVPRINTF(int severity, int domain, const char *fmt, va_list ap); 59 | 60 | void 61 | DHEXDUMP(int severity, int domain, const unsigned char *data, int data_len, const char *heading); 62 | 63 | void 64 | logger_ffmpeg(void *ptr, int level, const char *fmt, va_list ap); 65 | 66 | void 67 | logger_libevent(int severity, const char *msg); 68 | 69 | #ifdef HAVE_ALSA 70 | void 71 | logger_alsa(const char *file, int line, const char *function, int err, const char *fmt, ...); 72 | #endif 73 | 74 | void 75 | logger_reinit(void); 76 | 77 | int 78 | logger_severity(void); 79 | 80 | void 81 | logger_domains(void); 82 | 83 | void 84 | logger_detach(void); 85 | 86 | int 87 | logger_init(char *file, char *domains, int severity, char *logformat); 88 | 89 | void 90 | logger_deinit(void); 91 | 92 | 93 | #endif /* !__LOGGER_H__ */ 94 | -------------------------------------------------------------------------------- /src/mdns.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __MDNS_H__ 3 | #define __MDNS_H__ 4 | 5 | #include "misc.h" 6 | 7 | enum mdns_options 8 | { 9 | // Test connection to device and only call back if successful 10 | MDNS_CONNECTION_TEST = (1 << 1), 11 | // Only browse for ipv4 services 12 | MDNS_IPV4ONLY = (1 << 2), 13 | }; 14 | 15 | typedef void (* mdns_browse_cb)(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt); 16 | 17 | /* 18 | * Start a mDNS client 19 | * Call only from the main thread! 20 | * 21 | * @return 0 on success, -1 on error 22 | */ 23 | int 24 | mdns_init(void); 25 | 26 | /* 27 | * Removes registered services, stops service browsers and stop the mDNS client 28 | * Call only from the main thread! 29 | * 30 | */ 31 | void 32 | mdns_deinit(void); 33 | 34 | /* 35 | * Register (announce) a service with mDNS 36 | * Call only from the main thread! 37 | * 38 | * @in name Name of service, e.g. "My Music on Debian" 39 | * @in type Type of service to announce, e.g. "_daap._tcp" 40 | * @in port Port of the service 41 | * @in txt Pointer to array of strings with txt key/values ("Version=1") 42 | * for DNS-SD TXT. The array must be terminated by a NULL pointer. 43 | * @return 0 on success, -1 on error 44 | */ 45 | int 46 | mdns_register(char *name, char *type, int port, char **txt); 47 | 48 | /* 49 | * Register a CNAME record, it will be an alias for hostname 50 | * Call only from the main thread! 51 | * 52 | * @in name The CNAME alias, e.g. "myserver.local" 53 | * @return 0 on success, -1 on error 54 | */ 55 | int 56 | mdns_cname(char *name); 57 | 58 | /* 59 | * Start a service browser, a callback will be made when the service changes state 60 | * Call only from the main thread! 61 | * 62 | * @in type Type of service to look for, e.g. "_raop._tcp" 63 | * @in flags See mdns_options (only supported by Avahi implementation) 64 | * @in cb Callback when service state changes (e.g. appears/disappears) 65 | * @return 0 on success, -1 on error 66 | */ 67 | int 68 | mdns_browse(char *type, mdns_browse_cb cb, enum mdns_options flags); 69 | 70 | #endif /* !__MDNS_H__ */ 71 | -------------------------------------------------------------------------------- /src/misc_json.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Christian Meffert 3 | * 4 | * Some code included below is in the public domain, check comments 5 | * in the file. 6 | * 7 | * Pieces of code adapted from mt-daapd: 8 | * Copyright (C) 2003-2007 Ron Pedde (ron@pedde.com) 9 | * 10 | * This program is free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 | */ 24 | 25 | 26 | #ifndef SRC_MISC_JSON_H_ 27 | #define SRC_MISC_JSON_H_ 28 | 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | // Convenience macro so that instead of calling jparse with an array of keys 37 | // to follow, you can call JPARSE_SELECT(haystack, "key1", "key2"...) 38 | #define JPARSE_SELECT(haystack, ...) jparse_select(haystack, (const char *[]){__VA_ARGS__, NULL}) 39 | 40 | json_object * 41 | jparse_select(json_object *haystack, const char *keys[]); 42 | 43 | void 44 | jparse_free(json_object *haystack); 45 | 46 | bool 47 | jparse_contains_key(json_object *haystack, const char *key, json_type type); 48 | 49 | int 50 | jparse_array_from_obj(json_object *haystack, const char *key, json_object **needle); 51 | 52 | const char * 53 | jparse_str_from_obj(json_object *haystack, const char *key); 54 | 55 | int 56 | jparse_int_from_obj(json_object *haystack, const char *key); 57 | 58 | int 59 | jparse_bool_from_obj(json_object *haystack, const char *key); 60 | 61 | time_t 62 | jparse_time_from_obj(json_object *haystack, const char *key); 63 | 64 | const char * 65 | jparse_str_from_array(json_object *array, int index, const char *key); 66 | 67 | json_object * 68 | jparse_obj_from_evbuffer(struct evbuffer *evbuf); 69 | 70 | #endif /* SRC_MISC_JSON_H_ */ 71 | -------------------------------------------------------------------------------- /src/misc_xml.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_MISC_XML_H_ 2 | #define SRC_MISC_XML_H_ 3 | 4 | // This wraps libxml2 and adds some convenience functions 5 | 6 | typedef void xml_node; 7 | 8 | char * 9 | xml_to_string(xml_node *top, const char *xml_declaration); 10 | 11 | xml_node * 12 | xml_from_string(const char *string); 13 | 14 | xml_node * 15 | xml_from_file(const char *path); 16 | 17 | void 18 | xml_free(xml_node *top); 19 | 20 | xml_node * 21 | xml_get_node(xml_node *top, const char *path); 22 | 23 | // Only returns sibling nodes that have the same name as input node 24 | xml_node * 25 | xml_get_next(xml_node *top, xml_node *node); 26 | 27 | const char * 28 | xml_get_val(xml_node *top, const char *path); 29 | 30 | const char * 31 | xml_get_attr(xml_node *top, const char *path, const char *name); 32 | 33 | // Will create a new XML document with the node as root if parent is NULL 34 | xml_node * 35 | xml_new_node(xml_node *parent, const char *name, const char *val); 36 | 37 | xml_node * 38 | xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...); 39 | 40 | // Adds a text node to parent, which must be an element node 41 | void 42 | xml_new_text(xml_node *parent, const char *val); 43 | 44 | #endif /* SRC_MISC_XML_H_ */ 45 | -------------------------------------------------------------------------------- /src/mpd.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __MPD_H__ 3 | #define __MPD_H__ 4 | 5 | 6 | int 7 | mpd_init(void); 8 | 9 | void 10 | mpd_deinit(void); 11 | 12 | #endif /* !__MPD_H__ */ 13 | -------------------------------------------------------------------------------- /src/outputs/airplay_events.h: -------------------------------------------------------------------------------- 1 | #ifndef __AIRPLAY_EVENTS_H__ 2 | #define __AIRPLAY_EVENTS_H__ 3 | 4 | int 5 | airplay_events_listen(const char *name, const char *address, unsigned short port, const uint8_t *key, size_t key_len); 6 | 7 | int 8 | airplay_events_init(void); 9 | 10 | void 11 | airplay_events_deinit(void); 12 | 13 | #endif /* !__AIRPLAY_EVENTS_H__ */ 14 | -------------------------------------------------------------------------------- /src/outputs/plist_wrap.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Convenience wrappers for libplist 3 | * 4 | */ 5 | 6 | #include 7 | 8 | static void 9 | wplist_dict_add_uint(plist_t node, const char *key, uint64_t val) 10 | { 11 | plist_t add = plist_new_uint(val); 12 | plist_dict_set_item(node, key, add); 13 | } 14 | 15 | static void 16 | wplist_dict_add_string(plist_t node, const char *key, const char *val) 17 | { 18 | plist_t add = plist_new_string(val); 19 | plist_dict_set_item(node, key, add); 20 | } 21 | 22 | static void 23 | wplist_dict_add_bool(plist_t node, const char *key, bool val) 24 | { 25 | plist_t add = plist_new_bool(val); 26 | plist_dict_set_item(node, key, add); 27 | } 28 | 29 | static void 30 | wplist_dict_add_data(plist_t node, const char *key, uint8_t *data, size_t len) 31 | { 32 | plist_t add = plist_new_data((const char *)data, len); 33 | plist_dict_set_item(node, key, add); 34 | } 35 | 36 | static int 37 | wplist_to_bin(uint8_t **data, size_t *len, plist_t node) 38 | { 39 | char *out = NULL; 40 | uint32_t out_len = 0; 41 | 42 | plist_to_bin(node, &out, &out_len); 43 | if (!out) 44 | return -1; 45 | 46 | *data = (uint8_t *)out; 47 | *len = out_len; 48 | 49 | return 0; 50 | } 51 | 52 | static int 53 | wplist_from_evbuf(plist_t *node, struct evbuffer *evbuf) 54 | { 55 | uint8_t *data = evbuffer_pullup(evbuf, -1); 56 | size_t len = evbuffer_get_length(evbuf); 57 | plist_t out = NULL; 58 | 59 | plist_from_bin((char *)data, (uint32_t)len, &out); 60 | if (!out) 61 | return -1; 62 | 63 | *node = out; 64 | 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /src/parsers/.gitignore: -------------------------------------------------------------------------------- 1 | *_lexer.[ch] 2 | *_parser.[ch] 3 | -------------------------------------------------------------------------------- /src/remote_pairing.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __REMOTE_PAIRING_H__ 3 | #define __REMOTE_PAIRING_H__ 4 | 5 | #define REMOTE_ERROR -1 6 | #define REMOTE_INVALID_PIN -2 7 | 8 | void 9 | remote_pairing_kickoff(char **arglist); 10 | 11 | int 12 | remote_pairing_pair(const char *pin); 13 | 14 | char * 15 | remote_pairing_get_name(void); 16 | 17 | int 18 | remote_pairing_init(void); 19 | 20 | void 21 | remote_pairing_deinit(void); 22 | 23 | #endif /* !__REMOTE_PAIRING_H__ */ 24 | -------------------------------------------------------------------------------- /src/rng.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __RNG_H__ 3 | #define __RNG_H__ 4 | 5 | struct rng_ctx { 6 | int32_t iy; 7 | int32_t iv[32]; /* shuffle array */ 8 | int32_t seed; 9 | }; 10 | 11 | 12 | void 13 | rng_init(struct rng_ctx *ctx); 14 | 15 | int32_t 16 | rng_rand(struct rng_ctx *ctx); 17 | 18 | int32_t 19 | rng_rand_range(struct rng_ctx *ctx, int32_t min, int32_t max); 20 | 21 | void 22 | rng_shuffle_int(struct rng_ctx *ctx, int *values, int len); 23 | 24 | #endif /* !__RNG_H__ */ 25 | 26 | -------------------------------------------------------------------------------- /src/smartpl_query.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __SMARTPL_QUERY_H__ 3 | #define __SMARTPL_QUERY_H__ 4 | 5 | 6 | struct smartpl { 7 | char *title; 8 | char *query_where; 9 | char *having; 10 | char *order; 11 | int limit; 12 | }; 13 | 14 | int 15 | smartpl_query_parse_file(struct smartpl *smartpl, const char *file); 16 | 17 | int 18 | smartpl_query_parse_string(struct smartpl *smartpl, const char *expression); 19 | 20 | void 21 | free_smartpl(struct smartpl *smartpl, int content_only); 22 | 23 | #endif /* __SMARTPL_QUERY_H__ */ 24 | -------------------------------------------------------------------------------- /src/websocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Christian Meffert 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | 20 | #ifndef SRC_WEBSOCKET_H_ 21 | #define SRC_WEBSOCKET_H_ 22 | 23 | int 24 | websocket_init(void); 25 | 26 | void 27 | websocket_deinit(void); 28 | 29 | #endif /* SRC_WEBSOCKET_H_ */ 30 | -------------------------------------------------------------------------------- /src/worker.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __WORKER_H__ 3 | #define __WORKER_H__ 4 | 5 | #include 6 | 7 | /* The worker thread is made for running asyncronous tasks from a real time 8 | * thread. 9 | 10 | * The worker_execute() function will trigger a callback from the worker thread. 11 | * Before returning the function will copy the argument given, so the caller 12 | * does not need to preserve them. However, if the argument contains pointers to 13 | * data, the caller must either make sure that the data remains valid until the 14 | * callback (which can free it), or make sure the callback does not refer to it. 15 | * 16 | * @param cb the function to call from the worker thread 17 | * @param cb_arg arguments for callback 18 | * @param arg_size size of the arguments given 19 | * @param delay how much in seconds to delay the execution 20 | */ 21 | void 22 | worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay); 23 | 24 | /* Can be called within a callback to get the worker thread's event base 25 | */ 26 | struct event_base * 27 | worker_evbase_get(void); 28 | 29 | int 30 | worker_init(void); 31 | 32 | void 33 | worker_deinit(void); 34 | 35 | #endif /* !__WORKER_H__ */ 36 | -------------------------------------------------------------------------------- /web-src/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # dist directory 64 | dist/ 65 | 66 | # Visual Studio Code files 67 | .vscode/ -------------------------------------------------------------------------------- /web-src/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /web-src/eslint.config.js: -------------------------------------------------------------------------------- 1 | import eslintConfigPrettier from 'eslint-config-prettier' 2 | import globals from 'globals' 3 | import js from '@eslint/js' 4 | import pluginVue from 'eslint-plugin-vue' 5 | 6 | export default [ 7 | { 8 | files: ['src/**/*.js', 'src/**/.vue'], 9 | languageOptions: { 10 | globals: { 11 | ...globals.node 12 | } 13 | } 14 | }, 15 | eslintConfigPrettier, 16 | js.configs.all, 17 | ...pluginVue.configs['flat/recommended'], 18 | { 19 | rules: { 20 | camelcase: 'off', 21 | 'consistent-this': 'off', 22 | 'id-length': 'off', 23 | 'max-lines': 'off', 24 | 'max-lines-per-function': 'off', 25 | 'max-statements': 'off', 26 | 'no-bitwise': 'off', 27 | 'no-magic-numbers': 'off', 28 | 'no-nested-ternary': 'off', 29 | 'no-plusplus': 'off', 30 | 'no-ternary': 'off', 31 | 'no-undef': 'off', 32 | 'no-unused-vars': ['error', { args: 'none', caughtErrors: 'none' }], 33 | 'one-var': 'off', 34 | 'sort-keys': 'off', 35 | 'vue/html-self-closing': 'off', 36 | 'vue/max-attributes-per-line': 'off', 37 | 'vue/prop-name-casing': 'off' 38 | } 39 | } 40 | ] 41 | -------------------------------------------------------------------------------- /web-src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | OwnTone 19 | 20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /web-src/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*"] 3 | } 4 | -------------------------------------------------------------------------------- /web-src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "owntone-web", 3 | "version": "2.0.1", 4 | "type": "module", 5 | "scripts": { 6 | "serve": "vite --port 3000 --host", 7 | "build": "vite build --base='./'", 8 | "lint": "eslint", 9 | "dev": "vite", 10 | "format": "prettier . --write", 11 | "i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/i18n/**/*.json\"", 12 | "preview": "vite preview" 13 | }, 14 | "dependencies": { 15 | "@aacassandra/vue3-progressbar": "^1.0.3", 16 | "@mdi/js": "^7.4.47", 17 | "@ts-pro/vue-eternal-loading": "^1.3.1", 18 | "axios": "^1.7.4", 19 | "bulma": "^0.9.4", 20 | "bulma-switch": "^2.0.4", 21 | "luxon": "^3.4.4", 22 | "mdi-vue": "^3.0.13", 23 | "pinia": "^2.1.7", 24 | "reconnectingwebsocket": "^1.0.0", 25 | "spotify-web-api-js": "^1.5.2", 26 | "vue": "^3.4.23", 27 | "vue-i18n": "^9.13.1", 28 | "vue-router": "^4.3.2", 29 | "vue3-click-away": "^1.2.4", 30 | "vue3-lazyload": "^0.3.8", 31 | "vuedraggable": "^4.1.0" 32 | }, 33 | "devDependencies": { 34 | "@intlify/unplugin-vue-i18n": "^4.0.0", 35 | "@vitejs/plugin-vue": "^5.0.4", 36 | "eslint": "^9.1.0", 37 | "eslint-config-prettier": "^9.1.0", 38 | "eslint-plugin-vue": "^9.25.0", 39 | "prettier": "^3.2.5", 40 | "sass": "^1.75.0", 41 | "vite": "^6.2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /web-src/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/web-src/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /web-src/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/web-src/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /web-src/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/web-src/public/apple-touch-icon.png -------------------------------------------------------------------------------- /web-src/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /web-src/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/web-src/public/favicon-16x16.png -------------------------------------------------------------------------------- /web-src/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/web-src/public/favicon-32x32.png -------------------------------------------------------------------------------- /web-src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/web-src/public/favicon.ico -------------------------------------------------------------------------------- /web-src/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 15 | 18 | 20 | 22 | 24 | 26 | 27 | 28 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /web-src/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owntone/owntone-server/4154a20cda662074ebe122f95bf8033153ed9752/web-src/public/mstile-150x150.png -------------------------------------------------------------------------------- /web-src/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OwnTone", 3 | "short_name": "OwnTone", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /web-src/src/components/ControlDropdown.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /web-src/src/components/ControlSlider.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 35 | -------------------------------------------------------------------------------- /web-src/src/components/CoverArtwork.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 53 | -------------------------------------------------------------------------------- /web-src/src/components/IndexButtonList.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /web-src/src/components/ListArtists.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /web-src/src/components/ListArtistsSpotify.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /web-src/src/components/ListComposers.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /web-src/src/components/ListGenres.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /web-src/src/components/ListItemQueueItem.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /web-src/src/components/ListPlaylists.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /web-src/src/components/ListPlaylistsSpotify.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /web-src/src/components/ModalDialog.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /web-src/src/components/ModalDialogDirectory.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /web-src/src/components/NavbarItemLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 39 | -------------------------------------------------------------------------------- /web-src/src/components/NotificationList.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 44 | 45 | 63 | -------------------------------------------------------------------------------- /web-src/src/components/PlayerButtonConsume.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /web-src/src/components/PlayerButtonLyrics.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /web-src/src/components/PlayerButtonNext.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /web-src/src/components/PlayerButtonPlayPause.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /web-src/src/components/PlayerButtonPrevious.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /web-src/src/components/PlayerButtonRepeat.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /web-src/src/components/PlayerButtonSeekBack.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 61 | -------------------------------------------------------------------------------- /web-src/src/components/PlayerButtonSeekForward.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 61 | -------------------------------------------------------------------------------- /web-src/src/components/PlayerButtonShuffle.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /web-src/src/components/TabsSearch.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /web-src/src/filter/index.js: -------------------------------------------------------------------------------- 1 | import { DateTime, Duration } from 'luxon' 2 | import i18n from '@/i18n' 3 | 4 | const { t, locale } = i18n.global 5 | 6 | export const filters = { 7 | channels(value) { 8 | if (value === 1) { 9 | return t('filter.mono') 10 | } 11 | if (value === 2) { 12 | return t('filter.stereo') 13 | } 14 | if (!value) { 15 | return '' 16 | } 17 | return t('filter.channels', { value }) 18 | }, 19 | cursor(path, size = 20) { 20 | const viewbox = 24 21 | const center = size / 2 22 | return `url("data:image/svg+xml,%3Csvg width='${size}' height='${size}' viewBox='0 0 ${viewbox} ${viewbox}' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='${path}'/%3E%3C/svg%3E") ${center} ${center}, auto` 23 | }, 24 | date(value) { 25 | return DateTime.fromISO(value) 26 | .setLocale(locale.value) 27 | .toLocaleString(DateTime.DATE_FULL) 28 | }, 29 | datetime(value) { 30 | return DateTime.fromISO(value) 31 | .setLocale(locale.value) 32 | .toLocaleString(DateTime.DATETIME_MED) 33 | }, 34 | durationInDays(value) { 35 | const minutes = Math.floor(value / 60000) 36 | if (minutes > 1440) { 37 | return Duration.fromObject({ minutes }) 38 | .shiftTo('days', 'hours', 'minutes') 39 | .toHuman() 40 | } else if (minutes > 60) { 41 | return Duration.fromObject({ minutes }) 42 | .shiftTo('hours', 'minutes') 43 | .toHuman() 44 | } 45 | return Duration.fromObject({ minutes }).shiftTo('minutes').toHuman() 46 | }, 47 | durationInHours(value) { 48 | const format = value >= 3600000 ? 'h:mm:ss' : 'm:ss' 49 | return Duration.fromMillis(value).toFormat(format) 50 | }, 51 | number(value) { 52 | return value.toLocaleString(locale.value) 53 | }, 54 | timeFromNow(value) { 55 | const diff = DateTime.now().diff(DateTime.fromISO(value)) 56 | return this.durationInDays(diff.as('milliseconds')) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /web-src/src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n' 2 | 3 | /* 4 | * All i18n resources specified in the plugin `include` option can be loaded 5 | * at once using the import syntax. 6 | */ 7 | import messages from '@intlify/unplugin-vue-i18n/messages' 8 | 9 | export default createI18n({ 10 | availableLocales: ('de', 'en', 'fr', 'zh-CN', 'zh-TW'), 11 | fallbackLocale: 'en', 12 | fallbackWarn: false, 13 | legacy: false, 14 | locale: navigator.language, 15 | messages, 16 | missingWarn: false 17 | }) 18 | -------------------------------------------------------------------------------- /web-src/src/lib/Audio.js: -------------------------------------------------------------------------------- 1 | export default { 2 | audio: null, 3 | play(url) { 4 | this.audio = new Audio(`${String(url || '')}?x=${Date.now()}`) 5 | this.audio.crossOrigin = 'anonymous' 6 | const context = new (window.AudioContext || window.webkitAudioContext)() 7 | const source = context.createMediaElementSource(this.audio) 8 | source.connect(context.destination) 9 | this.audio.addEventListener('canplay', () => { 10 | context.resume().then(() => { 11 | this.audio.play() 12 | }) 13 | }) 14 | this.audio.load() 15 | }, 16 | setVolume(volume) { 17 | if (this.audio) { 18 | this.audio.volume = Math.max(0, Math.min(1, Number(volume) || 0)) 19 | } 20 | }, 21 | stop() { 22 | try { 23 | this.audio?.pause() 24 | } catch (error) { 25 | //Do nothing 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web-src/src/lib/SVGRenderer.js: -------------------------------------------------------------------------------- 1 | const toColor = (string) => { 2 | let hash = 0 3 | for (const char of string) { 4 | hash = char.charCodeAt(0) + ((hash << 5) - hash) 5 | } 6 | return (hash & 0x00ffffff).toString(16) 7 | } 8 | 9 | const luminance = (color) => 10 | [0.2126, 0.7152, 0.0722].reduce( 11 | (value, factor, index) => 12 | value + Number(`0x${color.slice(index * 2, index * 2 + 2)}`) * factor, 13 | 0 14 | ) / 255 15 | 16 | export const renderSVG = (data) => { 17 | const color = toColor(data.alternate), 18 | svg = ` 21 | 22 | 25 | ${data.caption} 26 | 27 | ` 28 | return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}` 29 | } 30 | -------------------------------------------------------------------------------- /web-src/src/main.js: -------------------------------------------------------------------------------- 1 | import './mystyles.scss' 2 | import App from './App.vue' 3 | import VueClickAway from 'vue3-click-away' 4 | import VueLazyLoad from 'vue3-lazyload' 5 | import VueProgressBar from '@aacassandra/vue3-progressbar' 6 | import { createApp } from 'vue' 7 | import { createPinia } from 'pinia' 8 | import { filters } from './filter' 9 | import i18n from './i18n' 10 | import { icons } from './icons' 11 | import mdiVue from 'mdi-vue/v3' 12 | import { router } from './router' 13 | 14 | const app = createApp(App) 15 | .use(createPinia()) 16 | .use(router) 17 | .use(VueProgressBar) 18 | .use(VueClickAway) 19 | .use(VueLazyLoad, { log: false }) 20 | .use(mdiVue, { icons }) 21 | .use(i18n) 22 | 23 | app.config.globalProperties.$filters = filters 24 | app.mount('#app') 25 | -------------------------------------------------------------------------------- /web-src/src/pages/PageAudiobooksAlbums.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /web-src/src/pages/PageAudiobooksArtists.vue: -------------------------------------------------------------------------------- 1 |