Repository: TheQwertiest/foo_spotify Branch: master Commit: ef3c0dd9fff1 Files: 226 Total size: 1.9 MB Directory structure: gitextract_nrm_lg86/ ├── .appveyor.yml ├── .bandit ├── .codacy.yml ├── .gitattributes ├── .gitignore ├── .gitmodules ├── .license_index.txt ├── CHANGELOG.md ├── LICENSE ├── README.md ├── THIRD_PARTY_NOTICES.md ├── VERSION ├── foo_spotify/ │ ├── .clang-format │ ├── .clang-tidy │ ├── backend/ │ │ ├── audio_buffer.cpp │ │ ├── audio_buffer.h │ │ ├── libspotify_backend.cpp │ │ ├── libspotify_backend.h │ │ ├── libspotify_backend_user.h │ │ ├── libspotify_key.h │ │ ├── libspotify_wrapper.h │ │ ├── spotify_instance.cpp │ │ ├── spotify_instance.h │ │ ├── spotify_object.cpp │ │ ├── spotify_object.h │ │ ├── webapi_auth.cpp │ │ ├── webapi_auth.h │ │ ├── webapi_auth_scopes.cpp │ │ ├── webapi_auth_scopes.h │ │ ├── webapi_backend.cpp │ │ ├── webapi_backend.h │ │ ├── webapi_cache.cpp │ │ ├── webapi_cache.h │ │ └── webapi_objects/ │ │ ├── webapi_album.cpp │ │ ├── webapi_album.h │ │ ├── webapi_artist.cpp │ │ ├── webapi_artist.h │ │ ├── webapi_image.cpp │ │ ├── webapi_image.h │ │ ├── webapi_media_objects.h │ │ ├── webapi_paging_object.cpp │ │ ├── webapi_paging_object.h │ │ ├── webapi_playlist_track.cpp │ │ ├── webapi_playlist_track.h │ │ ├── webapi_restriction.cpp │ │ ├── webapi_restriction.h │ │ ├── webapi_track.cpp │ │ ├── webapi_track.h │ │ ├── webapi_track_link.cpp │ │ ├── webapi_track_link.h │ │ ├── webapi_user.cpp │ │ └── webapi_user.h │ ├── component_defines.h │ ├── component_guids.h │ ├── component_paths.cpp │ ├── component_paths.h │ ├── component_urls.h │ ├── dllmain.cpp │ ├── fb2k/ │ │ ├── acfu_integration.cpp │ │ ├── advanced_config.cpp │ │ ├── advanced_config.h │ │ ├── album_art.cpp │ │ ├── config.cpp │ │ ├── config.h │ │ ├── file_info_filler.cpp │ │ ├── file_info_filler.h │ │ ├── filesystem.cpp │ │ ├── input.cpp │ │ ├── playback.cpp │ │ ├── playback.h │ │ ├── playlist.cpp │ │ └── playlist_loader.cpp │ ├── foo_spotify.rc │ ├── foo_spotify.rc2 │ ├── foo_spotify.vcxproj │ ├── foo_spotify.vcxproj.filters │ ├── packages.config │ ├── resource.h │ ├── stdafx.cpp │ ├── stdafx.h │ ├── ui/ │ │ ├── ui_ipref_tab.h │ │ ├── ui_not_auth.cpp │ │ ├── ui_not_auth.h │ │ ├── ui_pref_tab_auth.cpp │ │ ├── ui_pref_tab_auth.h │ │ ├── ui_pref_tab_manager.cpp │ │ ├── ui_pref_tab_manager.h │ │ ├── ui_pref_tab_playback.cpp │ │ └── ui_pref_tab_playback.h │ └── utils/ │ ├── abort_manager.cpp │ ├── abort_manager.h │ ├── async_mutex.hpp │ ├── cred_prompt.cpp │ ├── cred_prompt.h │ ├── json_macro_fix.h │ ├── json_std_extenders.h │ ├── rps_limiter.cpp │ ├── rps_limiter.h │ ├── secure_vector.h │ ├── sleeper.cpp │ └── sleeper.h ├── libspotify/ │ ├── ChangeLog │ ├── LICENSE │ ├── README │ ├── docs/ │ │ ├── html/ │ │ │ ├── annotated.html │ │ │ ├── api_8h.html │ │ │ ├── browse_8c-example.html │ │ │ ├── classes.html │ │ │ ├── doxygen.css │ │ │ ├── examples.html │ │ │ ├── files.html │ │ │ ├── functions.html │ │ │ ├── functions_vars.html │ │ │ ├── globals.html │ │ │ ├── globals_0x69.html │ │ │ ├── globals_0x73.html │ │ │ ├── globals_0x74.html │ │ │ ├── globals_defs.html │ │ │ ├── globals_enum.html │ │ │ ├── globals_eval.html │ │ │ ├── globals_func.html │ │ │ ├── globals_type.html │ │ │ ├── group__album.html │ │ │ ├── group__albumbrowse.html │ │ │ ├── group__artist.html │ │ │ ├── group__artistbrowse.html │ │ │ ├── group__error.html │ │ │ ├── group__image.html │ │ │ ├── group__inbox.html │ │ │ ├── group__link.html │ │ │ ├── group__playlist.html │ │ │ ├── group__search.html │ │ │ ├── group__session.html │ │ │ ├── group__toplist.html │ │ │ ├── group__track.html │ │ │ ├── group__types.html │ │ │ ├── group__user.html │ │ │ ├── index.html │ │ │ ├── jukebox_8c-example.html │ │ │ ├── modules.html │ │ │ ├── search_8c-example.html │ │ │ ├── structsp__audio__buffer__stats.html │ │ │ ├── structsp__audioformat.html │ │ │ ├── structsp__offline__sync__status.html │ │ │ ├── structsp__playlist__callbacks.html │ │ │ ├── structsp__playlistcontainer__callbacks.html │ │ │ ├── structsp__session__callbacks.html │ │ │ ├── structsp__session__config.html │ │ │ ├── structsp__subscribers.html │ │ │ ├── tabs.css │ │ │ └── toplist_8c-example.html │ │ └── images/ │ │ └── spotify-core.txt │ ├── examples/ │ │ ├── Makefile │ │ ├── Randomify/ │ │ │ ├── English.lproj/ │ │ │ │ ├── InfoPlist.strings │ │ │ │ └── MainMenu.xib │ │ │ ├── Randomify-Info.plist │ │ │ ├── Randomify.xcodeproj/ │ │ │ │ └── project.pbxproj │ │ │ ├── RandomifyAppDelegate.h │ │ │ ├── RandomifyAppDelegate.m │ │ │ ├── SpotifyPlaylist.h │ │ │ ├── SpotifyPlaylist.m │ │ │ ├── SpotifySession.h │ │ │ ├── SpotifySession.m │ │ │ ├── SpotifyTrack.h │ │ │ ├── SpotifyTrack.m │ │ │ └── main.m │ │ ├── appkey.c │ │ ├── common.mk │ │ ├── jukebox/ │ │ │ ├── Makefile │ │ │ ├── alsa-audio.c │ │ │ ├── audio.c │ │ │ ├── audio.h │ │ │ ├── dummy-audio.c │ │ │ ├── jukebox.c │ │ │ ├── openal-audio.c │ │ │ ├── osx/ │ │ │ │ └── jukebox.xcodeproj/ │ │ │ │ └── project.pbxproj │ │ │ ├── osx-audio.c │ │ │ ├── playtrack.c │ │ │ └── queue.h │ │ ├── localfiles/ │ │ │ ├── Makefile │ │ │ ├── main.c │ │ │ └── main.h │ │ ├── spshell/ │ │ │ ├── Makefile │ │ │ ├── browse.c │ │ │ ├── cmd.c │ │ │ ├── cmd.h │ │ │ ├── inbox.c │ │ │ ├── osx/ │ │ │ │ └── spshell.xcodeproj/ │ │ │ │ └── project.pbxproj │ │ │ ├── playlist.c │ │ │ ├── scrobbling.c │ │ │ ├── search.c │ │ │ ├── spshell.c │ │ │ ├── spshell.h │ │ │ ├── spshell_posix.c │ │ │ ├── spshell_win32.c │ │ │ ├── star.c │ │ │ ├── test.c │ │ │ ├── toplist.c │ │ │ └── win32/ │ │ │ ├── spshell.vcproj │ │ │ ├── spshell.vcxproj │ │ │ └── spshell.vcxproj.filters │ │ └── stub/ │ │ ├── Makefile │ │ ├── main.c │ │ └── main.h │ ├── include/ │ │ └── libspotify/ │ │ └── api.h │ ├── lib/ │ │ └── libspotify.lib │ └── licenses.xhtml ├── licenses/ │ ├── JSON for Modern C++.txt │ ├── LibSpotify.txt │ ├── PFC.txt │ ├── WTL.txt │ ├── fmt.txt │ ├── foobar2000 SDK.txt │ ├── range-v3.txt │ ├── ring-span-lite.txt │ └── span-lite.txt ├── props/ │ ├── local_dependencies/ │ │ └── libspotify.props │ └── submodules/ │ ├── fb2k_utils.props │ └── submodules.props ├── scripts/ │ ├── README.md │ ├── call_wrapper.py │ ├── download_submodules.py │ ├── pack_component.py │ ├── setup.py │ └── update_gh_pages.py └── workspaces/ └── foo_spotify.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .appveyor.yml ================================================ #---------------------------------# # general configuration # #---------------------------------# # version format version: 1.0.{build}-{branch} # branches to build branches: # blacklist except: - gh-pages # Skipping commits affecting specific files (GitHub only). More details here: /docs/appveyor-yml skip_commits: # Default `skip` messages are applied even on tag builds with APPVEYOR_IGNORE_COMMIT_FILTERING_ON_TAG==true message: /\[_skip ci_\]|\[_ci skip_\]|\[_skip_ci_\]|\[_ci_skip_\]/ files: - README.md - THIRD_PARTY_NOTICES.md # - docs/* # - '**/*.png' # - '**/*.jpg' # - '**/*.jpeg' # - '**/*.bmp' # - '**/*.gif' # - '**/*.js' # - '**/*.txt' # - '**/*.md' # Maximum number of concurrent jobs for the project max_jobs: 1 #---------------------------------# # environment configuration # #---------------------------------# # Build worker image (VM template) image: Visual Studio 2019 # environment variables environment: APPVEYOR_IGNORE_COMMIT_FILTERING_ON_TAG: true git_user_email: qwertiest@mail.ru git_user_name: TheQwertiest GITHUB_TOKEN: secure: gBfHQnRcGcTS0yQXWOHhKfDkTZfIBwPIDjTsCQOGmOSPkRqs1QPYBX47BjCvJKim # this is how to allow failing jobs in the matrix matrix: fast_finish: true # set this flag to immediately finish build once one of the jobs fails. cache: - workspaces\packages -> **\packages.config - c:\tools\vcpkg\installed # scripts that run after cloning repository install: # vcpkg - cd C:\Tools\vcpkg - git pull - .\bootstrap-vcpkg.bat - cd %APPVEYOR_BUILD_FOLDER% - vcpkg remove --outdated --recurse - cmd: set VCPKG_PLATFORM_TOOLSET=v142 - vcpkg install cpprestsdk:x86-windows-static - vcpkg integrate install # GitHub CLI - choco install gh -y - refreshenv - gh config set -h github.com git_protocol https # PIP - py -3 -m pip install semver # Component - py -u scripts\setup.py #---------------------------------# # build configuration # #---------------------------------# # build platform, i.e. x86, x64, Any CPU. This setting is optional. platform: - Win32 # build Configuration, i.e. Debug, Release, etc. configuration: - Debug - Release # Build settings, not to be confused with "before_build" and "after_build". # "project" is relative to the original build directory and not influenced by directory changes in "before_build". build: parallel: true # enable MSBuild parallel builds project: workspaces\foo_spotify.sln # path to Visual Studio solution or project # MSBuild verbosity level verbosity: normal # scripts to run before build before_build: - nuget restore workspaces\foo_spotify.sln # to run your custom scripts instead of automatic MSBuild build_script: # scripts to run after build (working directory and environment changes are persisted from the previous steps) after_build: # scripts to run *after* solution is built and *before* automatic packaging occurs (web apps, NuGet packages, Azure Cloud Services) before_package: - if '%configuration%' == 'Debug' ( py -u scripts\pack_component.py --debug && ren "_result\%platform%_%configuration%\foo_spotify.fb2k-component" "foo_spotify.fb2k-component_debug" ) else ( py -u scripts\pack_component.py ) # to disable automatic builds #build: off #---------------------------------# # artifacts configuration # #---------------------------------# artifacts: # pushing a single file with environment variable in path and "Deployment name" specified - path: _result\$(platform)_$(configuration)\foo_spotify.fb2k-component name: SPTF package (Release) - path: _result\$(platform)_$(configuration)\foo_spotify_pdb.zip name: SPTF PDB package (Release) - path: _result\$(platform)_$(configuration)\foo_spotify.fb2k-component_debug name: SPTF package (Debug) #---------------------------------# # deployment configuration # #---------------------------------# # providers: Local, FTP, WebDeploy, AzureCS, AzureBlob, S3, NuGet, Environment # provider names are case-sensitive! deploy: description: 'Dummy description' provider: GitHub auth_token: $(GITHUB_TOKEN) artifact: /foo_spotify.*/ # upload all NuGet packages to release assets prerelease: true on: branch: master # release from master branch only appveyor_repo_tag: true # deploy on tag push only # scripts to run before deployment before_deploy: # scripts to run after deployment after_deploy: # to run your custom scripts instead of provider deployments deploy_script: # to disable deployment # deploy: off #---------------------------------# # global handlers # #---------------------------------# # on successful build on_success: # issues clean up - ps: | If ($env:APPVEYOR_REPO_TAG -eq $true -and $env:CONFIGURATION -eq "Release") { py -u submodules\fb2k_utils\scripts\close_gh_issues.py } # gh-pages - ps: | If ($env:APPVEYOR_REPO_TAG -eq $true -and $env:CONFIGURATION -eq "Release") { git config --global credential.helper store Add-Content "$env:USERPROFILE\.git-credentials" "https://$($env:GITHUB_TOKEN):x-oauth-basic@github.com`n" git config --global user.email $env:git_user_email git config --global user.name $env:git_user_name git clone -q --depth=1 -b gh-pages https://github.com/$($env:APPVEYOR_REPO_NAME).git gh-pages py -u scripts\update_gh_pages.py cd gh-pages git add -A 2>&1 git commit -q -m "Updated documentation: $env:APPVEYOR_REPO_TAG_NAME ($env:APPVEYOR_REPO_COMMIT)" git push -q origin gh-pages cd $env:APPVEYOR_BUILD_FOLDER } # on build failure on_failure: # after build failure or success on_finish: #---------------------------------# # notifications # #---------------------------------# notifications: ================================================ FILE: .bandit ================================================ skips: ['B101','B404','B602','B607'] ================================================ FILE: .codacy.yml ================================================ engines: prospector: enabled: true python_version: 3 pylint: enabled: true python_version: 3 exclude_paths: - 'libspotify/**' - 'submodules/**' ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain *.zip filter=lfs diff=lfs merge=lfs -text libspotify/* linguist-detectable=false scripts/* linguist-detectable=false ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io/api/visualstudio ### VisualStudio ### ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results /_* *.fb2k-component [Dd]ebug/ [Dd]ebugPublic/ [Dd]ebug FB2K/ [Rr]elease/ [Rr]eleases/ [Rr]elease FB2K/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json project.fragment.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc ### VisualStudio Patch ### build/ ================================================ FILE: .gitmodules ================================================ [submodule "submodules/acfu-sdk"] path = submodules/acfu-sdk url = https://github.com/3dyd/acfu-sdk/ [submodule "submodules/fmt"] path = submodules/fmt url = https://github.com/fmtlib/fmt branch = release [submodule "submodules/foobar2000"] path = submodules/foobar2000 url = https://github.com/TheQwertiest/foobar2000-sdk [submodule "submodules/pfc"] path = submodules/pfc url = https://github.com/TheQwertiest/pfc [submodule "submodules/range"] path = submodules/range url = https://github.com/ericniebler/range-v3 [submodule "submodules/span"] path = submodules/span url = https://github.com/martinmoene/span-lite [submodule "submodules/json"] path = submodules/json url = https://github.com/nlohmann/json.git branch = master [submodule "submodules/wtl"] path = submodules/wtl url = https://git.code.sf.net/p/wtl/git [submodule "submodules/fb2k_utils"] path = submodules/fb2k_utils url = https://github.com/TheQwertiest/fb2k_utils.git [submodule "submodules/ring-span"] path = submodules/ring-span url = https://github.com/martinmoene/ring-span-lite.git ================================================ FILE: .license_index.txt ================================================ foobar2000 SDK: other fmt: other JSON for Modern C++: MIT LibSpotify: other PFC: zlib range-v3: BSL-1.0 ring-span-lite: BSL-1.0 span-lite: BSL-1.0 WTL: MS-PL ================================================ FILE: CHANGELOG.md ================================================ # Changelog #### Table of Contents - [Unreleased](#unreleased) - [1.1.3](#113---2021-02-18) - [1.1.2](#112---2020-11-03) - [1.1.1](#111---2020-10-27) - [1.1.0](#110---2020-10-26) - [1.0.2](#102---2020-10-04) - [1.0.1](#101---2020-10-03) - [1.0.0](#100---2020-10-02) ___ ## [Unreleased][] ## [1.1.3][] - 2021-02-18 ### Fixed - Fixed broken import-by-URL handling ([#47](https://github.com/TheQwertiest/foo_spotify/issues/47)). - Fixed some issues with keyboard navigation in component menu in Preferences. ## [1.1.2][] - 2020-11-03 ### Changed - Better Web API handling in attempt to avoid 429 errors: - Pre-emptive batch artist update on album import ([#36](https://github.com/TheQwertiest/foo_spotify/issues/36)). ### Fixed - Unreasonably high CPU usage in some scenarios ([#35](https://github.com/TheQwertiest/foo_spotify/issues/35)). - Rps limiter is not working ([#34](https://github.com/TheQwertiest/foo_spotify/issues/34)). - Extra silence at the end of track ([#33](https://github.com/TheQwertiest/foo_spotify/issues/33)). ## [1.1.1][] - 2020-10-27 ### Changed - Better Web API handling in attempt to avoid 429 errors: - Pre-emptive batch artist update on playlist import ([#32](https://github.com/TheQwertiest/foo_spotify/issues/32)). ## [1.1.0][] - 2020-10-26 ### Added - *Play* the artist: adds top tracks for the artist ([#15](https://github.com/TheQwertiest/foo_spotify/issues/15)). - `%codec%` metadata field ([#14](https://github.com/TheQwertiest/foo_spotify/issues/14)). - More options in `Preferences`: - Cache size configuration ([#23](https://github.com/TheQwertiest/foo_spotify/issues/23)). - Spotify `private mode` option ([#13](https://github.com/TheQwertiest/foo_spotify/issues/13)). - Spotify normalization option ([#12](https://github.com/TheQwertiest/foo_spotify/issues/12)). ### Changed - Better Web API handling in attempt to avoid 429 errors: - RPS limit ([#27](https://github.com/TheQwertiest/foo_spotify/issues/27)). - `retry-after` response header handling ([#11](https://github.com/TheQwertiest/foo_spotify/issues/11)). - Pre-emptive batch track update on playlist change ([#30](https://github.com/TheQwertiest/foo_spotify/issues/30)). - Pre-emptive batch artist update on playlist change ([#31](https://github.com/TheQwertiest/foo_spotify/issues/31)). - Improved error message when track is not available in user's country ([#8](https://github.com/TheQwertiest/foo_spotify/issues/8)). ### Fixed - Can't relogin without closing Preferences dialog ([#24](https://github.com/TheQwertiest/foo_spotify/issues/24)). - Dynamic info change is signaled too frequently, despite data being the same ([#22](https://github.com/TheQwertiest/foo_spotify/issues/22)). - Can't play tracks when buffering is enabled in fb2k ([#21](https://github.com/TheQwertiest/foo_spotify/issues/21)). - Crash: Illegal operation ([#19](https://github.com/TheQwertiest/foo_spotify/issues/19)). - Multi-value tags are not displayed correctly ([#17](https://github.com/TheQwertiest/foo_spotify/issues/17)). - Can't import playlists with local tracks ([#16](https://github.com/TheQwertiest/foo_spotify/issues/16), [#18](https://github.com/TheQwertiest/foo_spotify/issues/18)). ## [1.0.2][] - 2020-10-04 ### Added - Proxy support ([#6](https://github.com/TheQwertiest/foo_spotify/issues/6)). - Adjust bit-rate via `Preferences` ([#5](https://github.com/TheQwertiest/foo_spotify/issues/5)). - Bit-rate metadata for playing track ([#4](https://github.com/TheQwertiest/foo_spotify/issues/4)). ### Fixed - Fails to open track that has null `preview_url` ([#3](https://github.com/TheQwertiest/foo_spotify/issues/3)). ## [1.0.1][] - 2020-10-03 ### Fixed - LibSpotify login doesn't always work ([#1](https://github.com/TheQwertiest/foo_spotify/issues/1)). ## [1.0.0][] - 2020-10-02 Initial release. [unreleased]: https://github.com/TheQwertiest/foo_spotify/compare/v1.1.3...HEAD [1.1.3]: https://github.com/TheQwertiest/foo_spotify/compare/v1.1.2...v1.1.3 [1.1.2]: https://github.com/TheQwertiest/foo_spotify/compare/v1.1.1...v1.1.2 [1.1.1]: https://github.com/TheQwertiest/foo_spotify/compare/v1.1.0...v1.1.1 [1.1.0]: https://github.com/TheQwertiest/foo_spotify/compare/v1.0.2...v1.1.0 [1.0.2]: https://github.com/TheQwertiest/foo_spotify/compare/v1.0.1...v1.0.2 [1.0.1]: https://github.com/TheQwertiest/foo_spotify/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/TheQwertiest/foo_spotify/commits/master ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018-2020 Yuri Shutenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Spotify Integration [![version][version_badge]][changelog] [![Build status][appveyor_badge]](https://ci.appveyor.com/project/TheQwertiest/foo-spotify/branch/master) [![CodeFactor][codefactor_badge]](https://www.codefactor.io/repository/github/theqwertiest/foo_spotify/overview/master) [![Codacy Badge][codacy_badge]](https://www.codacy.com/gh/TheQwertiest/foo_spotify/dashboard?utm_source=github.com&utm_medium=referral&utm_content=TheQwertiest/foo_spotify&utm_campaign=Badge_Grade) **!!! This component is no longer being developed. See [#68](https://github.com/TheQwertiest/foo_spotify/issues/68) for more info !!!** This is a component for the [foobar2000](https://www.foobar2000.org) audio player, which allows to play tracks from Spotify. Visit [component homepage](https://theqwertiest.github.io/foo_spotify/) for more info. [changelog]: CHANGELOG.md [version_badge]: https://img.shields.io/github/release/theqwertiest/foo_spotify.svg [appveyor_badge]: https://ci.appveyor.com/api/projects/status/t5bhoxmfgavhq81m/branch/master?svg=true [codacy_badge]: https://app.codacy.com/project/badge/Grade/c2d3b3a99ce640ad8c1512784e15910f [codefactor_badge]: https://www.codefactor.io/repository/github/theqwertiest/foo_spotify/badge/master ================================================ FILE: THIRD_PARTY_NOTICES.md ================================================ Spotify Integration uses third-party libraries or other resources that may be distributed under licenses different than the Spotify Integration software. The linked notices are provided for information only. - [foobar2000 SDK - other](licenses/foobar2000%20SDK.txt) - [fmt - other](licenses/fmt.txt) - [JSON for Modern C++ - MIT](licenses/JSON%20for%20Modern%20C%2B%2B.txt) - [LibSpotify - other](licenses/LibSpotify.txt) - [PFC - zlib](licenses/PFC.txt) - [range-v3 - BSL-1.0](licenses/range-v3.txt) - [ring-span-lite - BSL-1.0](licenses/ring-span-lite.txt) - [span-lite - BSL-1.0](licenses/span-lite.txt) - [WTL - MS-PL](licenses/WTL.txt) ================================================ FILE: VERSION ================================================ 1.1.4-beta ================================================ FILE: foo_spotify/.clang-format ================================================ --- BasedOnStyle: Mozilla AccessModifierOffset: '-4' AlwaysBreakAfterDefinitionReturnType: 'false' AlignAfterOpenBracket: Align AlignConsecutiveAssignments: 'false' AlignConsecutiveMacros: 'true' AlignEscapedNewlines: Left AlignOperands: 'true' AllowShortBlocksOnASingleLine: 'false' AllowShortCaseLabelsOnASingleLine: 'false' AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: 'false' AllowShortLoopsOnASingleLine: 'false' AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: 'true' BinPackParameters: 'true' BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Allman BreakBeforeInheritanceComma: 'true' BreakConstructorInitializers: BeforeComma ColumnLimit: '0' ConstructorInitializerIndentWidth: '4' ContinuationIndentWidth: '4' Cpp11BracedListStyle: 'false' FixNamespaceComments: 'true' IncludeBlocks: Regroup IncludeCategories: # precompiled headers - Regex: '^' Priority: -1 # "header" - Regex: '"[[:alnum:]_]+' Priority: 1 # - Regex: '^<(acfu-sdk|cpprest|columns_ui-sdk|fmt|foobar2000|js|libspotify|nlohmann|nonstd|range|tim|qwr)/' Priority: 4 # - Regex: '^<(atl.*\.h)>' Priority: 5 # | - Regex: '<[[:alnum:]_]+/.+\.(h|hpp)>' Priority: 2 # | - Regex: '<[[:alnum:]_]+\.(h|hpp)>' Priority: 3 #
- Regex: '<[[:alnum:]_]+>' Priority: 6 IncludeIsMainRegex: '$' IndentCaseLabels: false IndentPPDirectives: AfterHash IndentWidth: '4' IndentWrappedFunctionNames: false Language: Cpp MacroBlockBegin: "^BEGIN_*" MacroBlockEnd: "^END_*" MaxEmptyLinesToKeep: '1' PointerAlignment: Left ReflowComments: 'true' SortIncludes: 'false' SpaceAfterCStyleCast: 'false' SpaceAfterTemplateKeyword : 'true' SpaceBeforeAssignmentOperators: 'true' SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: 'false' SpaceInEmptyParentheses: 'false' SpacesInAngles: 'false' SpacesInCStyleCastParentheses: 'false' SpacesInParentheses: 'true' SpacesInSquareBrackets: 'false' SortIncludes: 'true' Standard: Cpp11 TabWidth: '4' TypenameMacros: ['STDMETHOD', 'STDMETHODIMP_'] UseTab: Never ... ================================================ FILE: foo_spotify/.clang-tidy ================================================ Checks: '-*, bugprone-*, cert-*, clang-analyzer-*, google-*, -google-build-using-namespace, -google-explicit-constructor, -google-readability-todo, -google-runtime-references, hicpp-*, -hicpp-explicit-conversions, -hicpp-no-array-decay, -hicpp-signed-bitwise, -hicpp-uppercase-literal-suffix, misc-*, -misc-non-private-member-variables-in-classes, llvm-*, -llvm-include-order, -llvm-header-guard, -llvm-namespace-comment, modernize-*, -modernize-use-trailing-return-type, performance-*, portability-*, readability-*, -readability-else-after-return, -readability-implicit-bool-conversion, -readability-magic-numbers, -readability-named-parameter, -readability-redundant-access-specifiers, -readability-uppercase-literal-suffix' ================================================ FILE: foo_spotify/backend/audio_buffer.cpp ================================================ #include #include "audio_buffer.h" #include namespace sptf { AudioBuffer::AudioBuffer( AbortManager& abortManager ) : abortManager_( abortManager ) { } bool AudioBuffer::write( AudioChunkHeader header, const uint16_t* data ) { { std::lock_guard lock( posMutex_ ); const size_t readPos = readPos_; const size_t writePos = writePos_; const size_t waterMark = waterMark_; const size_t writeSize = k_headerSizeInU16 + header.size; const auto writeData = [&]( size_t writePos ) { const auto curBufferPos = begin_ + writePos; std::copy( &header, &header + 1, reinterpret_cast( curBufferPos ) ); std::copy( data, data + header.size, curBufferPos + k_headerSizeInU16 ); }; // TODO: simplify the code if ( writePos >= readPos ) // write leads { if ( size_ - writePos >= writeSize ) { writeData( writePos ); writePos_ = writePos + writeSize; waterMark_ = size_; } else // wrap-around { if ( !readPos || readPos - 1 < writeSize ) { return false; } writeData( 0 ); writePos_ = writeSize; waterMark_ = writePos; } } else // read leads { if ( readPos - 1 - writePos < writeSize ) { return false; } writeData( writePos ); writePos_ = writePos + writeSize; } } dataCv_.notify_all(); return true; } void AudioBuffer::write_end() { uint16_t dummy{}; write( AudioChunkHeader{ 0, 0, 0, 1 }, &dummy ); } bool AudioBuffer::has_data() const { std::lock_guard lock( posMutex_ ); return has_data_no_lock(); } bool AudioBuffer::wait_for_data( abort_callback& abort ) { const auto abortableScope = abortManager_.GetAbortableScope( [&] { dataCv_.notify_all(); }, abort ); std::unique_lock lock( posMutex_ ); dataCv_.wait( lock, [&] { return ( has_data_no_lock() || abort.is_aborting() ); } ); return has_data_no_lock(); } void AudioBuffer::clear() { { std::lock_guard lock( posMutex_ ); readPos_ = 0; writePos_ = 0; waterMark_ = size_; } dataCv_.notify_all(); } bool AudioBuffer::has_data_no_lock() const { return ( readPos_ != writePos_ ); } } // namespace sptf ================================================ FILE: foo_spotify/backend/audio_buffer.h ================================================ #pragma once #include #include #include #include namespace sptf { class AbortManager; // TODO: bench and replace with lock-free if needed class AudioBuffer { static constexpr size_t k_maxBufferSize = 8UL * 1024 * 1024; public: #pragma pack( push ) #pragma pack( 1 ) struct AudioChunkHeader { uint32_t sampleRate; uint16_t channels; uint32_t size; uint16_t eof; }; #pragma pack( pop ) private: static constexpr size_t k_headerSizeInU16 = sizeof( AudioChunkHeader ) / sizeof( uint16_t ); public: AudioBuffer( AbortManager& abortManager ); ~AudioBuffer() = default; bool write( AudioChunkHeader header, const uint16_t* data ); void write_end(); template bool read( Fn fn ); bool has_data() const; bool wait_for_data( abort_callback& abort ); void clear(); private: bool has_data_no_lock() const; private: AbortManager& abortManager_; std::array buffer_; uint16_t* begin_ = buffer_.data(); static constexpr size_t size_ = k_maxBufferSize; mutable std::mutex posMutex_; std::condition_variable dataCv_; size_t readPos_ = 0; size_t writePos_ = 0; size_t waterMark_ = size_; }; template bool sptf::AudioBuffer::read( Fn fn ) { std::lock_guard lock( posMutex_ ); size_t readPos = readPos_; const size_t writePos = writePos_; const size_t waterMark = waterMark_; if ( readPos == writePos ) { return false; } readPos = ( readPos == waterMark ? 0 : readPos ); const auto curBufferPos = begin_ + readPos; fn( *reinterpret_cast( curBufferPos ), curBufferPos + k_headerSizeInU16 ); readPos_ = readPos + k_headerSizeInU16 + reinterpret_cast( curBufferPos )->size; return true; } } // namespace sptf ================================================ FILE: foo_spotify/backend/libspotify_backend.cpp ================================================ #include #include "libspotify_backend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // see https://github.com/mopidy/mopidy-spotify for tips and stuff namespace fs = std::filesystem; using namespace sptf; namespace sptf { LibSpotify_Backend::LibSpotify_Backend( AbortManager& abortManager ) : abortManager_( abortManager ) , audioBuffer_( abortManager ) { if ( const auto settingsPath = path::LibSpotifySettings(); !fs::exists( settingsPath ) ) { fs::create_directories( settingsPath ); } // TODO: add fs error/exception checks const auto cachePath = path::LibSpotifyCache().u8string(); const auto settingsPath = path::LibSpotifySettings().u8string(); config_.api_version = SPOTIFY_API_VERSION, config_.cache_location = cachePath.c_str(); config_.settings_location = settingsPath.c_str(); config_.application_key = g_appkey; config_.application_key_size = sizeof( g_appkey ); config_.user_agent = "foobar2000-foo_spotify-" SPTF_VERSION; config_.userdata = this; config_.callbacks = &callbacks_; const auto proxyUrl = sptf::config::advanced::network_proxy.GetValue(); const auto proxyUsername = sptf::config::advanced::network_proxy_username.GetValue(); const auto proxyPassword = sptf::config::advanced::network_proxy_password.GetValue(); if ( !proxyUrl.empty() ) { config_.proxy = proxyUrl.c_str(); if ( !proxyUsername.empty() && !proxyPassword.empty() ) { config_.proxy_username = proxyUsername.c_str(); config_.proxy_password = proxyPassword.c_str(); } } /* config_.tracefile = ...; */ #define SPTF_ASSIGN_CALLBACK( callbacks, name ) \ callbacks.name = []( sp_session * pSession, auto... args ) -> auto \ { /** sp_session_userdata is assumed to be thread safe. */ \ return static_cast( sp_session_userdata( pSession ) )->name( args... ); \ } // dummy callbacks are needed to avoid libspotify crashes on sp_session_release #define SPTF_ASSIGN_DUMMY_CALLBACK( callbacks, name ) \ callbacks.name = []( auto... ) {} SPTF_ASSIGN_CALLBACK( callbacks_, logged_in ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, logged_out ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, metadata_updated ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, connection_error ); SPTF_ASSIGN_CALLBACK( callbacks_, message_to_user ); SPTF_ASSIGN_CALLBACK( callbacks_, notify_main_thread ); SPTF_ASSIGN_CALLBACK( callbacks_, music_delivery ); SPTF_ASSIGN_CALLBACK( callbacks_, play_token_lost ); SPTF_ASSIGN_CALLBACK( callbacks_, log_message ); SPTF_ASSIGN_CALLBACK( callbacks_, end_of_track ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, streaming_error ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, userinfo_updated ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, start_playback ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, stop_playback ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, get_audio_buffer_stats ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, offline_status_updated ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, offline_error ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, credentials_blob_updated ); SPTF_ASSIGN_CALLBACK( callbacks_, connectionstate_updated ); SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, scrobble_error ); SPTF_ASSIGN_CALLBACK( callbacks_, private_session_mode_changed ); #undef SPTF_ASSIGN_DUMMY_CALLBACK #undef SPTF_ASSIGN_CALLBACK // TODO: check if `sp_playlist_add_callbacks` works when implementing playlist handling { std::lock_guard lock( apiMutex_ ); const auto sp = sp_session_create( &config_, &pSpSession_ ); if ( sp != SP_ERROR_OK ) { throw qwr::QwrException( fmt::format( "sp_session_create failed: {}", sp_error_message( sp ) ) ); } } StartEventLoopThread(); RefreshBitrate(); RefreshNormalization(); RefreshCacheSize(); } void LibSpotify_Backend::Finalize() { StopEventLoopThread(); { std::lock_guard lk( backendUsersMutex_ ); for ( auto& pInput: backendUsers_ ) { pInput->Finalize(); } } { std::lock_guard lock( apiMutex_ ); sp_session_player_unload( pSpSession_ ); sp_session_release( pSpSession_ ); } } void LibSpotify_Backend::RegisterBackendUser( LibSpotify_BackendUser& input ) { std::lock_guard lk( backendUsersMutex_ ); assert( !backendUsers_.count( &input ) ); backendUsers_.emplace( &input ); } void LibSpotify_Backend::UnregisterBackendUser( LibSpotify_BackendUser& input ) { std::lock_guard lk( backendUsersMutex_ ); assert( backendUsers_.count( &input ) ); backendUsers_.erase( &input ); } sp_session* LibSpotify_Backend::GetInitializedSpSession( abort_callback& p_abort ) { assert( pSpSession_ ); if ( !Relogin( p_abort ) ) { ::fb2k::inMainThread( [&] { playback_control::get()->stop(); ui::NotAuthHandler::Get().ShowDialog(); } ); throw qwr::QwrException( "Failed to get authenticated Spotify session" ); } return pSpSession_; } sp_session* LibSpotify_Backend::GetWhateverSpSession() { assert( pSpSession_ ); return pSpSession_; } bool LibSpotify_Backend::Relogin( abort_callback& abort ) { { std::lock_guard lock( loginMutex_ ); if ( loginStatus_ == LoginStatus::logged_out ) { return false; } if ( loginStatus_ == LoginStatus::uninitialized ) { loginStatus_ = LoginStatus::login_in_process; const auto spRet = [&] { std::lock_guard lock( apiMutex_ ); return sp_session_relogin( pSpSession_ ); }(); if ( spRet == SP_ERROR_NO_CREDENTIALS ) { loginStatus_ = LoginStatus::logged_out; return false; } } } auto retStatus = WaitForLoginStatusUpdate( abort ); return ( retStatus.has_value() ? *retStatus : false ); } bool LibSpotify_Backend::LoginWithUI( HWND hWnd ) { // TODO: add abortable as a method argument assert( core_api::assert_main_thread() ); { std::lock_guard lock( loginMutex_ ); assert( loginStatus_ == LoginStatus::logged_out ); loginStatus_ = LoginStatus::login_in_process; isLoginBad_ = false; } std::optional retStatus; do { const auto wasLoginBad = [&] { std::lock_guard lock( loginMutex_ ); return isLoginBad_; }(); auto cpr = ShowCredentialsDialog( hWnd, wasLoginBad ? sp_error_message( SP_ERROR_BAD_USERNAME_OR_PASSWORD ) : nullptr ); if ( cpr->cancelled ) { { std::lock_guard lock( loginMutex_ ); loginStatus_ = LoginStatus::logged_out; } loginCv_.notify_all(); return false; } { std::lock_guard lock( apiMutex_ ); sp_session_login( pSpSession_, cpr->un.data(), cpr->pw.data(), true, nullptr ); } qwr::TimedAbortCallback tac( fmt::format( "{}: {}", SPTF_UNDERSCORE_NAME, "LibSpotify wait for login update" ) ); retStatus = WaitForLoginStatusUpdate( tac ); } while ( retStatus.has_value() && !*retStatus ); return ( retStatus.has_value() ? *retStatus : false ); } void LibSpotify_Backend::LogoutAndForget( abort_callback& abort ) { { std::lock_guard lgLogin( loginMutex_ ); if ( loginStatus_ != LoginStatus::logged_in ) { return; } loginStatus_ = LoginStatus::logout_in_process; } { std::lock_guard lgApi( apiMutex_ ); sp_session_logout( pSpSession_ ); sp_session_forget_me( pSpSession_ ); } WaitForLoginStatusUpdate( abort ); } std::string LibSpotify_Backend::GetLoggedInUserName() { { std::lock_guard lock( loginMutex_ ); if ( loginStatus_ != LoginStatus::logged_in ) { return ""; } } std::lock_guard lock( apiMutex_ ); // `sp_user_display_name` always returns canonical name: // https://stackoverflow.com/questions/23797162/sp-user-display-name-always-returns-canonical-name-even-when-user-is-loaded const char* email = sp_session_user_name( pSpSession_ ); if ( !email ) { return ""; } return email; } void LibSpotify_Backend::RefreshBitrate() { std::lock_guard lock( apiMutex_ ); const auto sp = sp_session_preferred_bitrate( pSpSession_, static_cast( static_cast( config::preferred_bitrate.GetValue() ) ) ); if ( sp != SP_ERROR_OK ) { qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, fmt::format( "sp_session_preferred_bitrate failed:\n{}", sp_error_message( sp ) ) ); } } void LibSpotify_Backend::RefreshNormalization() { std::lock_guard lock( apiMutex_ ); const auto sp = sp_session_set_volume_normalization( pSpSession_, config::enable_normalization ); if ( sp != SP_ERROR_OK ) { qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, fmt::format( "sp_session_set_volume_normalization failed:\n{}", sp_error_message( sp ) ) ); } } void LibSpotify_Backend::RefreshPrivateMode() { { std::lock_guard lock( loginMutex_ ); if ( loginStatus_ != LoginStatus::logged_in ) { // private session can be set only in logged in session return; } } { std::lock_guard lock( apiMutex_ ); RefreshPrivateModeNonBlocking(); } } void LibSpotify_Backend::RefreshCacheSize() { const auto sizeInMb = config::libspotify_cache_size_in_mb.GetValue(); const auto sizeInPercent = config::libspotify_cache_size_in_percent.GetValue(); const auto cacheSize = [sizeInMb, sizeInPercent]() -> uint32_t { if ( !sizeInMb && ( sizeInPercent == 10 || !sizeInPercent ) ) { // 0 - default LibSpotify behaviour return 0; } uint64_t dummy1; uint64_t dummy2; uint64_t freeBytes; auto bRet = GetDiskFreeSpaceEx( L"C:", (PULARGE_INTEGER)&dummy1, (PULARGE_INTEGER)&dummy2, (PULARGE_INTEGER)&freeBytes ); qwr::error::CheckWinApi( bRet, "GetDiskFreeSpaceEx" ); const auto freeMb = ( uint32_t )( freeBytes / ( 1024 * 1024 ) ); if ( !sizeInPercent ) { return std::min( sizeInMb, freeMb ); } else { const auto freeSpacePercented = ( uint32_t )( freeMb * ( 1.0 / sizeInPercent ) ); if ( sizeInMb ) { return std::min( sizeInMb, freeSpacePercented ); } else { return freeSpacePercented; } } }(); std::lock_guard lock( apiMutex_ ); const auto sp = sp_session_set_cache_size( pSpSession_, cacheSize ); if ( sp != SP_ERROR_OK ) { qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, fmt::format( "sp_session_set_cache_size failed:\n{}", sp_error_message( sp ) ) ); } } void LibSpotify_Backend::EventLoopThread() { int nextTimeout = INFINITE; while ( true ) { { std::unique_lock lock( workerMutex_ ); while ( !hasEvents_ && !shouldStopEventLoop_ ) { const auto ret = eventLoopCv_.wait_for( lock, std::chrono::milliseconds( nextTimeout ) ); if ( std::cv_status::timeout == ret ) { break; } } if ( shouldStopEventLoop_ ) { return; } hasEvents_ = false; } std::lock_guard lock( apiMutex_ ); sp_session_process_events( pSpSession_, &nextTimeout ); } } void LibSpotify_Backend::StartEventLoopThread() { assert( !pWorker_ ); pWorker_ = std::make_unique( &LibSpotify_Backend::EventLoopThread, this ); qwr::SetThreadName( *pWorker_, "SPTF Event Loop" ); } void LibSpotify_Backend::StopEventLoopThread() { if ( !pWorker_ ) { return; } { std::unique_lock lock( workerMutex_ ); shouldStopEventLoop_ = true; } eventLoopCv_.notify_all(); if ( pWorker_->joinable() ) { pWorker_->join(); } pWorker_.reset(); } std::optional LibSpotify_Backend::WaitForLoginStatusUpdate( abort_callback& abort ) { const auto abortableScope = abortManager_.GetAbortableScope( [&] { loginCv_.notify_all(); }, abort ); std::unique_lock lock( loginMutex_ ); loginCv_.wait( lock, [&] { return ( ( loginStatus_ != LoginStatus::login_in_process && loginStatus_ != LoginStatus::logout_in_process ) || abort.is_aborting() ); } ); if ( abort.is_aborting() ) { return std::nullopt; } return ( loginStatus_ == LoginStatus::logged_in ); } void LibSpotify_Backend::RefreshPrivateModeNonBlocking() { const auto sp = sp_session_set_private_session( pSpSession_, config::enable_private_mode ); if ( sp != SP_ERROR_OK ) { qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, fmt::format( "sp_session_set_private_session failed:\n{}", sp_error_message( sp ) ) ); } } void LibSpotify_Backend::AcquireDecoder( void* owner ) { std::lock_guard lk( decoderOwnerMutex_ ); if ( pDecoderOwner_ && pDecoderOwner_ != owner ) { throw exception_io_data( "Someone else is already decoding: Spotify does not support multiple concurrent decoders" ); } pDecoderOwner_ = owner; } void LibSpotify_Backend::ReleaseDecoder( void* owner ) { (void)owner; std::lock_guard lk( decoderOwnerMutex_ ); assert( owner == pDecoderOwner_ ); pDecoderOwner_ = nullptr; } AudioBuffer& LibSpotify_Backend::GetAudioBuffer() { return audioBuffer_; } void LibSpotify_Backend::log_message( const char* error ) { FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (log): " << error; } void LibSpotify_Backend::logged_in( sp_error error ) { if ( SP_ERROR_OK == error ) { return; } if ( SP_ERROR_BAD_USERNAME_OR_PASSWORD == error ) { std::lock_guard lock( loginMutex_ ); if ( loginStatus_ == LoginStatus::login_in_process ) { isLoginBad_ = true; loginStatus_ = LoginStatus::logged_out; } } else { { std::lock_guard lock( loginMutex_ ); loginStatus_ = LoginStatus::uninitialized; } qwr::ReportErrorWithPopup( SPTF_NAME, fmt::format( "Failed to login:\n{}", sp_error_message( error ) ) ); } } void LibSpotify_Backend::message_to_user( const char* message ) { qwr::ReportErrorWithPopup( SPTF_NAME, message ); } void LibSpotify_Backend::notify_main_thread() { { std::unique_lock lock( workerMutex_ ); hasEvents_ = true; } eventLoopCv_.notify_all(); } int LibSpotify_Backend::music_delivery( const sp_audioformat* format, const void* frames, int num_frames ) { if ( !num_frames ) { audioBuffer_.clear(); return 0; } assert( frames ); if ( num_frames == 22050 && !*(uint16_t*)frames ) { // a dirty hack to remove a 1 seconds silence that libspotify pads the end of a track with // See: https://github.com/mopidy/mopidy-spotify/pull/269 // and https://stackoverflow.com/questions/26014520/libspotify-c-sending-zeros-at-the-end-of-track return num_frames; } if ( !audioBuffer_.write( AudioBuffer::AudioChunkHeader{ (uint16_t)format->sample_rate, (uint16_t)format->channels, ( uint16_t )( num_frames * format->channels ) }, static_cast( frames ) ) ) { return 0; } return num_frames; } void LibSpotify_Backend::end_of_track() { audioBuffer_.write_end(); } void LibSpotify_Backend::play_token_lost() { ::fb2k::inMainThread2( [&] { playback_control::get()->pause( true ); } ); qwr::ReportErrorWithPopup( SPTF_NAME, "Playback has been paused because your Spotify account is being used somewhere else." ); } void LibSpotify_Backend::connectionstate_updated() { switch ( sp_session_connectionstate( pSpSession_ ) ) { case SP_CONNECTION_STATE_LOGGED_IN: case SP_CONNECTION_STATE_OFFLINE: { { std::lock_guard lock( loginMutex_ ); loginStatus_ = LoginStatus::logged_in; } loginCv_.notify_all(); RefreshPrivateModeNonBlocking(); break; } case SP_CONNECTION_STATE_LOGGED_OUT: { { std::lock_guard lock( loginMutex_ ); if ( loginStatus_ == LoginStatus::login_in_process ) { // possible when invalid user/pass are supplied break; } loginStatus_ = LoginStatus::logged_out; } loginCv_.notify_all(); break; } default: { break; } } } void LibSpotify_Backend::private_session_mode_changed( bool is_private ) { // in case private mode expired if ( is_private == config::enable_private_mode ) { return; } RefreshPrivateModeNonBlocking(); } } // namespace sptf ================================================ FILE: foo_spotify/backend/libspotify_backend.h ================================================ #pragma once #include #include #include #include #include #include #include namespace sptf { class AbortManager; class LibSpotify_Backend { public: LibSpotify_Backend( AbortManager& abortManager ); LibSpotify_Backend( const LibSpotify_Backend& ) = delete; LibSpotify_Backend( LibSpotify_Backend&& ) = delete; ~LibSpotify_Backend() = default; void Finalize(); void RegisterBackendUser( LibSpotify_BackendUser& backendUser ); void UnregisterBackendUser( LibSpotify_BackendUser& backendUser ); void AcquireDecoder( void* owner ); void ReleaseDecoder( void* owner ); AudioBuffer& GetAudioBuffer(); sp_session* GetInitializedSpSession( abort_callback& abort ); sp_session* GetWhateverSpSession(); template auto ExecSpMutex( Fn func, Args&&... args ) -> decltype( auto ) { std::lock_guard lock( apiMutex_ ); return func( std::forward( args )... ); } bool Relogin( abort_callback& abort ); bool LoginWithUI( HWND hWnd ); void LogoutAndForget( abort_callback& abort ); std::string GetLoggedInUserName(); void RefreshBitrate(); void RefreshNormalization(); void RefreshPrivateMode(); void RefreshCacheSize(); private: void EventLoopThread(); void StartEventLoopThread(); void StopEventLoopThread(); std::optional WaitForLoginStatusUpdate( abort_callback& abort ); void RefreshPrivateModeNonBlocking(); // callbacks void log_message( const char* error ); void logged_in( sp_error error ); void message_to_user( const char* error ); void notify_main_thread(); int music_delivery( const sp_audioformat* format, const void* frames, int num_frames ); void end_of_track(); void play_token_lost(); void connectionstate_updated(); void private_session_mode_changed( bool is_private ); private: AbortManager& abortManager_; sp_session_callbacks callbacks_{}; sp_session_config config_{}; std::mutex decoderOwnerMutex_; void* pDecoderOwner_ = nullptr; std::mutex apiMutex_; sp_session* pSpSession_ = nullptr; std::unique_ptr pWorker_; std::mutex workerMutex_; std::condition_variable eventLoopCv_; bool hasEvents_ = false; bool shouldStopEventLoop_ = false; std::mutex backendUsersMutex_; std::unordered_set backendUsers_; enum class LoginStatus { uninitialized, logged_in, logged_out, login_in_process, logout_in_process, }; std::mutex loginMutex_; std::condition_variable loginCv_; LoginStatus loginStatus_ = LoginStatus::uninitialized; bool isLoginBad_ = false; AudioBuffer audioBuffer_; }; } // namespace sptf ================================================ FILE: foo_spotify/backend/libspotify_backend_user.h ================================================ #pragma once namespace sptf { class LibSpotify_BackendUser { public: ~LibSpotify_BackendUser() = default; virtual void Finalize() = 0; }; } // namespace sptf ================================================ FILE: foo_spotify/backend/libspotify_key.h ================================================ #pragma once #include // clang-format off inline constexpr uint8_t g_appkey[] = { 0x01, 0xF8, 0x35, 0x9D, 0x45, 0x52, 0xD7, 0xA5, 0xC2, 0x91, 0xC0, 0x94, 0x18, 0x52, 0x81, 0x28, 0x6A, 0xA4, 0xAE, 0x77, 0x4B, 0xE9, 0x87, 0xC9, 0x05, 0x2D, 0x72, 0x11, 0xC2, 0x59, 0x18, 0x6F, 0xB3, 0x91, 0x4E, 0x63, 0xCE, 0x6E, 0xDA, 0xF5, 0xE3, 0xC7, 0xC6, 0xC9, 0x3B, 0x88, 0x37, 0x31, 0x3C, 0xDA, 0x71, 0xF1, 0x4C, 0x68, 0x03, 0x1B, 0xF8, 0xC0, 0x4A, 0xBA, 0x20, 0x0B, 0xBE, 0xC4, 0x2F, 0x49, 0x7A, 0xC5, 0x5C, 0xC4, 0x36, 0xF1, 0x94, 0xF7, 0xC6, 0x4F, 0x27, 0xFB, 0x5E, 0x31, 0x00, 0x31, 0x53, 0x52, 0x74, 0x0B, 0x64, 0x16, 0x4B, 0x70, 0xA6, 0x71, 0x6D, 0xC0, 0x71, 0x7F, 0x6B, 0xFD, 0xE5, 0xA4, 0x57, 0x99, 0x4A, 0x11, 0xB0, 0x0B, 0xBA, 0xC4, 0xF2, 0x9A, 0xB9, 0x33, 0xC7, 0x5F, 0x70, 0x6D, 0x73, 0xEA, 0xAB, 0x88, 0xD2, 0x83, 0xAC, 0x33, 0xA7, 0xFE, 0xDA, 0xD7, 0xF2, 0xAC, 0x04, 0x41, 0xAC, 0x7D, 0xEF, 0x88, 0x5A, 0x21, 0xEC, 0xD8, 0x02, 0x40, 0xB7, 0x0C, 0x3A, 0x42, 0x2C, 0x26, 0x17, 0x9D, 0x82, 0x43, 0x50, 0x07, 0x1F, 0x2E, 0xAF, 0x72, 0x88, 0x65, 0xDB, 0xA7, 0x83, 0xA6, 0x44, 0x06, 0x2E, 0x55, 0x08, 0xF2, 0x4C, 0x4A, 0x96, 0xF7, 0xD5, 0xFC, 0x57, 0x75, 0x5F, 0x1C, 0x1D, 0xB1, 0x35, 0x5A, 0x48, 0x83, 0x1F, 0x60, 0x65, 0xBD, 0x40, 0xDD, 0x03, 0x1D, 0x3A, 0x4E, 0xD6, 0xA8, 0xB8, 0x5E, 0xFC, 0x60, 0x59, 0xE0, 0xB3, 0xC5, 0x64, 0xD0, 0x2A, 0x20, 0x48, 0x24, 0x5E, 0x9B, 0xE4, 0xF0, 0x45, 0xAA, 0x30, 0xCE, 0xB3, 0x11, 0x45, 0xB0, 0x01, 0xDA, 0x82, 0x58, 0xFE, 0x4F, 0x97, 0xF2, 0x40, 0x4D, 0x8C, 0x83, 0x17, 0x8C, 0x97, 0xCF, 0x40, 0x17, 0xDB, 0xC7, 0x19, 0xEC, 0xFA, 0x01, 0x4F, 0xE6, 0x78, 0x3A, 0x54, 0x3F, 0xE9, 0x62, 0xA3, 0xF2, 0xDD, 0x0A, 0xDD, 0x90, 0xE4, 0x24, 0xE7, 0x57, 0x8C, 0x30, 0x18, 0x13, 0xA6, 0x9C, 0x52, 0x7D, 0xBE, 0x0B, 0x10, 0xEA, 0x0C, 0x53, 0x87, 0x9B, 0x26, 0xBA, 0xA7, 0x70, 0xAE, 0x88, 0x8A, 0xAA, 0x5E, 0x7C, 0x73, 0xC3, 0x08, 0x92, 0x29, 0xDC, 0xEA, 0x22, 0x25, 0x16, 0xB5, 0xF0, 0x89, 0xF0, 0x88, 0xBE, 0xAB, 0x84, 0x6C, 0xFE, 0xF1, 0xBE, 0x8A, 0x81, 0x19, 0x0A, 0xE7, 0x64, 0xC4, }; // clang-format on ================================================ FILE: foo_spotify/backend/libspotify_wrapper.h ================================================ #pragma once #include #include namespace sptf::wrapper { namespace internal { template struct SpotifyTraits { static_assert( "unknown specialization" ); }; template <> struct SpotifyTraits { static void AddRef( sp_track* album ) { sp_track_add_ref( album ); } static void Release( sp_track* album ) { sp_track_release( album ); } static bool IsLoaded( sp_track* album ) { return sp_track_is_loaded( album ); } }; template <> struct SpotifyTraits { static void AddRef( sp_link* link ) { sp_link_add_ref( link ); } static void Release( sp_link* link ) { sp_link_release( link ); } // does not have a "IsLoaded" method }; } // namespace internal template class Ptr { public: Ptr() = default; Ptr( T* ptr ) : ptr_( ptr ) { if ( ptr_ ) { internal::SpotifyTraits::AddRef( ptr_ ); } } Ptr( const Ptr& other ) : ptr_( other.ptr_ ) { if ( ptr_ ) { internal::SpotifyTraits::AddRef( ptr_ ); } } Ptr( Ptr&& other ) : ptr_( other.ptr_ ) { other.ptr_ = nullptr; } void Release() { auto ptr = ptr_; ptr_ = nullptr; if ( ptr ) { internal::SpotifyTraits::Release( ptr ); } } void Attach( T* ptr ) { if ( ptr_ ) { internal::SpotifyTraits::Release( ptr ); } ptr_ = ptr; } operator bool() const { return !!ptr_; } bool operator!() const { return !ptr_; } T* operator=( T* ptr ) { Release(); ptr_ = ptr; if ( ptr_ ) { internal::SpotifyTraits::AddRef( ptr_ ); } return ptr_; } operator T*() const { return ptr_; } private: T* ptr_ = nullptr; }; } // namespace sptf::wrapper ================================================ FILE: foo_spotify/backend/spotify_instance.cpp ================================================ #include #include "spotify_instance.h" #include #include #include #include #include #include #include namespace sptf { SpotifyInstance& SpotifyInstance::Get() { static SpotifyInstance si; return si; } void SpotifyInstance::Finalize() { std::lock_guard lg( mutex_ ); isFinalized_ = true; const auto finalize = []( auto& pElem ) { if ( pElem ) { try { pElem->Finalize(); } catch ( const std::exception& ) { } pElem.reset(); } }; if ( fb2k_playCallbacks_initialized_ ) { fb2k::PlayCallbacks::Finalize(); fb2k_playCallbacks_initialized_ = false; } finalize( pWebApi_backend_ ); finalize( pLibSpotify_backend_ ); finalize( pAbortManager_ ); finalize( pThreadPool_ ); } qwr::ThreadPool& SpotifyInstance::GetThreadPool() { InitializeAll(); assert( pThreadPool_ ); return *pThreadPool_; } AbortManager& SpotifyInstance::GetAbortManager() { InitializeAll(); assert( pAbortManager_ ); return *pAbortManager_; } LibSpotify_Backend& SpotifyInstance::GetLibSpotify_Backend() { InitializeAll(); assert( pLibSpotify_backend_ ); return *pLibSpotify_backend_; } WebApi_Backend& SpotifyInstance::GetWebApi_Backend() { InitializeAll(); assert( pWebApi_backend_ ); return *pWebApi_backend_; } void SpotifyInstance::InitializeAll() { std::lock_guard lg( mutex_ ); if ( isFinalized_ ) { throw qwr::QwrException( "foobar2000 is exiting" ); } if ( !pThreadPool_ ) { pThreadPool_ = std::make_unique( "SPTF Worker", 2 ); } if ( !pAbortManager_ ) { pAbortManager_ = std::make_unique(); } if ( !pLibSpotify_backend_ ) { pLibSpotify_backend_ = std::make_unique( *pAbortManager_ ); } if ( !pWebApi_backend_ ) { pWebApi_backend_ = std::make_unique( *pAbortManager_ ); } if ( !fb2k_playCallbacks_initialized_ ) { fb2k::PlayCallbacks::Initialize( *pLibSpotify_backend_ ); fb2k_playCallbacks_initialized_ = true; } } } // namespace sptf ================================================ FILE: foo_spotify/backend/spotify_instance.h ================================================ #pragma once #include namespace qwr { class ThreadPool; } namespace sptf { class AbortManager; class LibSpotify_Backend; class WebApi_Backend; namespace fb2k { class PlayCallbacks; } class SpotifyInstance { public: ~SpotifyInstance() = default; static SpotifyInstance& Get(); void Finalize(); qwr::ThreadPool& GetThreadPool(); AbortManager& GetAbortManager(); LibSpotify_Backend& GetLibSpotify_Backend(); WebApi_Backend& GetWebApi_Backend(); private: SpotifyInstance() = default; void InitializeAll(); private: std::mutex mutex_; bool isFinalized_ = false; std::unique_ptr pThreadPool_; std::unique_ptr pAbortManager_; std::unique_ptr pLibSpotify_backend_; std::unique_ptr pWebApi_backend_; bool fb2k_playCallbacks_initialized_ = false; }; } // namespace sptf ================================================ FILE: foo_spotify/backend/spotify_object.cpp ================================================ #include #include "spotify_object.h" #include using namespace std::literals::string_view_literals; namespace sptf { bool SpotifyObject::IsValid( std::string_view input ) { try { SpotifyObject tmp( input ); (void)tmp; return true; } catch ( const qwr::QwrException& ) { return false; } } SpotifyObject::SpotifyObject( std::string_view input ) { const auto schemaPrefix = "sptf://"sv; if ( input._Starts_with( schemaPrefix ) ) { input.remove_prefix( schemaPrefix.size() ); } const auto urlPrefix = "https://open.spotify.com/"sv; if ( input._Starts_with( urlPrefix ) ) { input.remove_prefix( urlPrefix.size() ); const auto ret = qwr::string::Split( input, '/' ); qwr::QwrException::ExpectTrue( ret.size() == 2, "Invalid URL" ); const auto localType = ret[0]; type = std::string( localType.data(), localType.size() ); const auto localId = [localId = ret[1]] { const auto pos = localId.find( '?' ); if ( pos == std::string_view::npos ) { return localId; } return localId.substr( 0, pos ); }(); qwr::QwrException::ExpectTrue( !localId.empty(), "Invalid Spotify object id: {}", localId ); id = std::string( localId.cbegin(), localId.cend() ); } else { const auto ret = qwr::string::Split( input, ':' ); qwr::QwrException::ExpectTrue( ret.size() == 3, "Invalid URI" ); qwr::QwrException::ExpectTrue( ret[0] == "spotify"sv, "Invalid URI" ); const auto localType = ret[1]; type = std::string( localType.data(), localType.size() ); const auto localId = ret[2]; qwr::QwrException::ExpectTrue( !localId.empty(), "Invalid Spotify object id: {}", localId ); id = std::string( localId.cbegin(), localId.cend() ); } } SpotifyObject::SpotifyObject( std::string_view type, std::string_view id ) : type( type ) , id( id ) { } std::string SpotifyObject::ToUri() const { return fmt::format( "spotify:{}:{}", type, id ); } std::string SpotifyObject::ToUrl() const { return fmt::format( "https://open.spotify.com/{}/{}", type, id ); } std::string SpotifyObject::ToSchema() const { return fmt::format( "sptf://spotify:{}:{}", type, id ); } SpotifyFilteredTrack::SpotifyFilteredTrack( std::string_view id ) : object_( "track", id ) { } SpotifyFilteredTrack SpotifyFilteredTrack::Parse( std::string_view input ) { if ( !IsValid( input, false ) ) { throw qwr::QwrException( "Unsupported input format: {}", input ); } SpotifyObject so( input ); assert( so.type == "track" ); return SpotifyFilteredTrack( so.id ); } const std::string& SpotifyFilteredTrack::Id() const { return object_.id; } std::string SpotifyFilteredTrack::ToUri() const { return object_.ToUri(); } std::string SpotifyFilteredTrack::ToUrl() const { return object_.ToUrl(); } std::string SpotifyFilteredTrack::ToSchema() const { return object_.ToSchema(); } bool SpotifyFilteredTrack::IsValid( std::string_view input, bool usePurePathOnly ) { const auto schemaPrefix = "sptf://"sv; if ( input._Starts_with( schemaPrefix ) ) { input.remove_prefix( schemaPrefix.size() ); } else if ( usePurePathOnly ) { return false; } return input._Starts_with( "spotify:track:"sv ); } } // namespace sptf ================================================ FILE: foo_spotify/backend/spotify_object.h ================================================ #pragma once #include namespace sptf { struct SpotifyObject { /// @throw qwr::QwrException SpotifyObject( std::string_view input ); /// @throw qwr::QwrException SpotifyObject( std::string_view type, std::string_view id ); std::string ToUri() const; std::string ToUrl() const; std::string ToSchema() const; static bool IsValid( std::string_view input ); std::string type; std::string id; }; class SpotifyFilteredTrack { public: /// @throw qwr::QwrException SpotifyFilteredTrack( std::string_view id ); /// @throw qwr::QwrException static SpotifyFilteredTrack Parse( std::string_view input ); const std::string& Id() const; std::string ToUri() const; std::string ToUrl() const; std::string ToSchema() const; static bool IsValid( std::string_view input, bool usePurePathOnly ); private: SpotifyObject object_; }; } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_auth.cpp ================================================ #include #include "webapi_auth.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO: move to props #pragma comment( lib, "bcrypt.lib" ) #pragma comment( lib, "Crypt32.lib" ) #pragma comment( lib, "winhttp.lib" ) #pragma comment( lib, "httpapi.lib" ) // TODO: add abortable to cts // TODO: clean up mutex handling - it's a mess here namespace fs = std::filesystem; using namespace sptf; namespace { constexpr wchar_t k_clientId[] = L"30826243a65f43f19b038fd65ffaa8b5"; } // namespace namespace { std::wstring GenerateCodeVerifier() { static constexpr wchar_t codeVerifierChars[] = L"abcdefghijklmnopqrstuvwxyz" L"ABCDEFGHIJKLMNOPQRSTUVWXYZ" L"1234567890" L"_.-~"; std::random_device rd; const auto size = std::uniform_int_distribution( 43, 128 )( rd ); std::wstring ret; ret.resize( size ); std::uniform_int_distribution charDist( 0, std::size( codeVerifierChars ) - 2 ); for ( auto& ch: ret ) { ch = codeVerifierChars[charDist( rd )]; } return ret; } std::vector Sha256Hash( nonstd::span data ) { // TODO: cleanup add error handling NTSTATUS status; BCRYPT_ALG_HANDLE alg_handle = nullptr; BCRYPT_HASH_HANDLE hash_handle = nullptr; std::vector hash; DWORD hash_len = 0; ULONG result_len = 0; status = BCryptOpenAlgorithmProvider( &alg_handle, BCRYPT_SHA256_ALGORITHM, nullptr, 0 ); if ( !NT_SUCCESS( status ) ) { goto cleanup; } status = BCryptGetProperty( alg_handle, BCRYPT_HASH_LENGTH, (PBYTE)&hash_len, sizeof( hash_len ), &result_len, 0 ); if ( !NT_SUCCESS( status ) ) { goto cleanup; } hash.resize( hash_len ); status = BCryptCreateHash( alg_handle, &hash_handle, nullptr, 0, nullptr, 0, 0 ); if ( !NT_SUCCESS( status ) ) { goto cleanup; } status = BCryptHashData( hash_handle, (PBYTE)data.data(), (ULONG)data.size(), 0 ); if ( !NT_SUCCESS( status ) ) { goto cleanup; } status = BCryptFinishHash( hash_handle, hash.data(), hash_len, 0 ); if ( !NT_SUCCESS( status ) ) { goto cleanup; } cleanup: if ( hash_handle ) { BCryptDestroyHash( hash_handle ); } if ( alg_handle ) { BCryptCloseAlgorithmProvider( alg_handle, 0 ); } return hash; } std::wstring GenerateChallengeCode( const std::wstring& codeVerifier ) { const auto code_u8 = qwr::unicode::ToU8( codeVerifier ); const auto hash = Sha256Hash( nonstd::span( reinterpret_cast( code_u8.data() ), code_u8.size() ) ); auto base64 = utility::conversions::to_base64( hash ); { while ( base64[base64.size() - 1] == L'=' ) { base64.resize( base64.size() - 1 ); } for ( auto& ch: base64 ) { switch ( ch ) { case L'+': { ch = L'-'; break; } case L'/': { ch = L'_'; break; } default: { break; } } } } return base64; } void OpenAuthConfirmationInBrowser( const std::wstring& url ) { ShellExecuteW( nullptr, L"open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL ); } } // namespace namespace sptf { struct AuthData { std::wstring accessToken; std::wstring refreshToken; std::chrono::time_point expiresAt = std::chrono::system_clock::now(); WebApiAuthScopes scopes; }; void to_json( nlohmann::json& j, const AuthData& p ) { j["access_token"] = p.accessToken; j["refresh_token"] = p.refreshToken; j["expires_at"] = std::chrono::time_point_cast( p.expiresAt ).time_since_epoch().count(); j["scopes"] = p.scopes; } void from_json( const nlohmann::json& j, AuthData& p ) { j.at( "access_token" ).get_to( p.accessToken ); j.at( "refresh_token" ).get_to( p.refreshToken ); p.expiresAt = std::chrono::time_point( std::chrono::minutes( j.at( "expires_at" ).get() ) ); if ( j.contains( "scopes" ) ) { j["scopes"].get_to( p.scopes ); } else { // for backwards compatibility WebApiAuthScopes scopes; scopes.playlist_read_collaborative = true; scopes.playlist_read_private = true; p.scopes = scopes; } } WebApiAuthorizer::WebApiAuthorizer( const web::http::client::http_client_config& config, AbortManager& abortManager ) : abortManager_( abortManager ) , client_( url::accountsApi, config ) { const auto authpath = path::WebApiSettings() / "auth.json"; if ( fs::exists( authpath ) ) { try { const auto data = qwr::file::ReadFileW( authpath, CP_UTF8, false ); const auto dataJson = nlohmann::json::parse( data ); dataJson.get_to( pAuthData_ ); } catch ( const std::exception& ) { pAuthData_.reset(); try { fs::remove( authpath ); } catch ( const fs::filesystem_error& ) { } } } } WebApiAuthorizer::~WebApiAuthorizer() { cts_.cancel(); StopResponseListener(); } bool WebApiAuthorizer::HasRefreshToken() const { std::lock_guard lock( accessTokenMutex_ ); return !!pAuthData_; } const std::wstring WebApiAuthorizer::GetAccessToken( abort_callback& abort ) { std::lock_guard lock( accessTokenMutex_ ); if ( !pAuthData_ ) { ::fb2k::inMainThread( [&] { playback_control::get()->stop(); ui::NotAuthHandler::Get().ShowDialog(); } ); throw qwr::QwrException( "Failed to get authenticated Spotify session" ); } UpdateRefreshToken_NonBlocking( abort ); assert( !pAuthData_->accessToken.empty() ); return pAuthData_->accessToken; } void WebApiAuthorizer::ClearAuth() { pAuthData_.reset(); codeVerifier_.clear(); state_.clear(); const auto authpath = path::WebApiSettings() / "auth.json"; if ( fs::exists( authpath ) ) { fs::remove( authpath ); } } void WebApiAuthorizer::CancelAuth() { // TODO: rethink cts handling: current scheme is not thread-safe. // Example: CancelAuth from preferences (and reassign cts_), while some other thread is using cts_. cts_.cancel(); cts_ = pplx::cancellation_token_source(); StopResponseListener(); ClearAuth(); } void WebApiAuthorizer::AuthenticateClean( const WebApiAuthScopes& scopes, std::function onResponseEnd ) { assert( core_api::is_main_thread() ); StopResponseListener(); StartResponseListener( onResponseEnd ); // codeVerifier_ = GenerateCodeVerifier(); web::uri_builder builder( url::accountsAuthenticate ); builder .append_query( L"client_id", web::uri::encode_data_string( k_clientId ), false ) .append_query( L"response_type", L"code" ) .append_query( L"redirect_uri", web::uri::encode_data_string( url::redirectUri ), false ) .append_query( L"code_challenge_method", L"S256" ) .append_query( L"code_challenge", GenerateChallengeCode( codeVerifier_ ) ) .append_query( L"scope", web::uri::encode_data_string( scopes.ToWebString() ), false ); // TODO: cookie? // builder.append_query( L"state", L"azaza" ); OpenAuthConfirmationInBrowser( builder.to_string() ); } void WebApiAuthorizer::AuthenticateClean_Cleanup() { StopResponseListener(); } void WebApiAuthorizer::UpdateRefreshToken( abort_callback& abort ) { std::lock_guard lock( accessTokenMutex_ ); UpdateRefreshToken_NonBlocking( abort ); } void WebApiAuthorizer::UpdateRefreshToken_NonBlocking( abort_callback& abort ) { assert( pAuthData_ ); if ( std::chrono::system_clock::now() + std::chrono::minutes( 1 ) < pAuthData_->expiresAt ) { return; } web::uri_builder builder; builder .append_query( L"grant_type", L"refresh_token" ) .append_query( L"refresh_token", pAuthData_->refreshToken ) .append_query( L"client_id", web::uri::encode_data_string( k_clientId ), false ); web::http::http_request req( web::http::methods::POST ); req.headers().set_content_type( L"application/x-www-form-urlencoded" ); req.set_request_uri( builder.to_uri() ); auto ctsToken = cts_.get_token(); auto localCts = Concurrency::cancellation_token_source::create_linked_source( ctsToken ); const auto abortableScope = abortManager_.GetAbortableScope( [&localCts] { localCts.cancel(); }, abort ); auto response = client_.request( req, cts_.get_token() ); HandleAuthenticationResponse( response.get() ); } pplx::task WebApiAuthorizer::CompleteAuthentication( const std::wstring& responseUrl ) { // TODO: final_action web::uri uri( responseUrl ); // TODO: check redirectUri? const auto data = web::uri::split_query( uri.query() ); if ( data.count( L"error" ) ) { throw qwr::QwrException( qwr::unicode::ToU8( fmt::format( L"Authentication failed: {}", data.at( L"error" ) ) ) ); } if ( !state_.empty() && ( !data.count( L"state" ) || data.at( L"state" ) != state_ ) ) { throw qwr::QwrException( "Malformed authentication response: `state` mismatch" ); } qwr::QwrException::ExpectTrue( data.count( L"code" ), L"Malformed authentication response: missing `code`" ); web::uri_builder builder; builder .append_query( L"client_id", k_clientId ) .append_query( L"grant_type", L"authorization_code" ) .append_query( L"code", data.at( L"code" ) ) .append_query( L"redirect_uri", web::uri::encode_data_string( url::redirectUri ), false ) .append_query( L"code_verifier", codeVerifier_, false ); web::http::http_request req( web::http::methods::POST ); req.headers().set_content_type( L"application/x-www-form-urlencoded" ); req.set_body( builder.query() ); const auto response = co_await client_.request( req, cts_.get_token() ); HandleAuthenticationResponse( response ); } void WebApiAuthorizer::StartResponseListener( std::function onResponseEnd ) { assert( !pListener_ ); pListener_ = std::make_unique( url::redirectUri ); pListener_->support( [this, onResponseEnd]( const auto& request ) { try { if ( request.request_uri().path() == L"/" && request.request_uri().query() != L"" ) { const auto callbackCaller = qwr::final_action( [&] { onResponseEnd(); } ); try { CompleteAuthentication( request.request_uri().to_string() ).wait(); web::http::http_response res; res.set_status_code( web::http::status_codes::OK ); res.headers().set_content_type( L"text/html; charset=utf-8" ); res.set_body( L"\n" L"\n" L"\n" L"\n" L"

foo_spotify was successfully authenticated!

\n " L"

You can close this tab now :)

\n " L"\n" L"" ); request.reply( res ).wait(); } catch ( const std::exception& e ) { pAuthData_.reset(); auto errorMsg = qwr::unicode::ToWide( std::string( e.what() ) ); { size_t pos = 0; while ( ( pos = errorMsg.find( L"\n", pos ) ) != std::string::npos ) { errorMsg.replace( pos, 1, L"
" ); pos += 4; } } web::http::http_response res; res.set_status_code( web::http::status_codes::OK ); res.headers().set_content_type( L"text/html; charset=utf-8" ); res.set_body( L"\n" L"\n" L"\n" L"

foo_spotify failed to authenticate!

\n" L"

Error:

\n" L"

" + errorMsg + "

\n" L"\n" L"" ); request.reply( res ).wait(); } } else { web::http::http_response res; res.set_status_code( web::http::status_codes::OK ); res.headers().set_content_type( L"text/html; charset=utf-8" ); res.set_body( L"

Congratulations! You found a useless page! o/

" ); request.reply( res ).wait(); } } catch ( ... ) { } } ); pListener_->open().wait(); } void WebApiAuthorizer::StopResponseListener() { if ( pListener_ ) { pListener_->close().wait(); pListener_.reset(); } } void WebApiAuthorizer::HandleAuthenticationResponse( const web::http::http_response& response ) { if ( response.status_code() != 200 ) { ClearAuth(); throw qwr::QwrException( L"{}: {}\n" L"Additional data: {}\n", (int)response.status_code(), response.reason_phrase(), [&]() -> std::wstring { try { const auto responseJson = nlohmann::json::parse( response.extract_string().get() ); return qwr::unicode::ToWide( responseJson.dump( 2 ) ); } catch ( ... ) { return response.to_string(); } }() ); } const auto responseJson = response.extract_json().get(); qwr::QwrException::ExpectTrue( responseJson.is_object(), L"Malformed authentication response: json is not an object" ); qwr::QwrException::ExpectTrue( responseJson.at( L"token_type" ).as_string() == L"Bearer", L"Malformed authentication response: invalid `token_type`: {}", responseJson.at( L"token_type" ).as_string() ); auto pAuthData = std::make_unique(); pAuthData->accessToken = responseJson.at( L"access_token" ).as_string(); pAuthData->refreshToken = responseJson.at( L"refresh_token" ).as_string(); pAuthData->expiresAt = std::chrono::system_clock::now() + std::chrono::seconds( responseJson.at( L"expires_in" ).as_integer() ); auto scopesSplit = qwr::string::Split( responseJson.at( L"scope" ).as_string(), L' ' ); pAuthData->scopes = WebApiAuthScopes( scopesSplit ); const auto settingsPath = path::WebApiSettings(); if ( !fs::exists( settingsPath ) ) { fs::create_directories( settingsPath ); } // not using component config because it's not saved immediately qwr::file::WriteFile( settingsPath / "auth.json", nlohmann::json( pAuthData ).dump( 2 ) ); pAuthData_ = std::move( pAuthData ); } } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_auth.h ================================================ #pragma once #include #include #include #include namespace web::http::experimental::listener { class http_listener; } namespace sptf { class AbortManager; struct WebApiAuthScopes; struct AuthData; class WebApiAuthorizer { public: WebApiAuthorizer( const web::http::client::http_client_config& config, AbortManager& abortManager ); ~WebApiAuthorizer(); bool HasRefreshToken() const; const std::wstring GetAccessToken( abort_callback& abort ); void ClearAuth(); void CancelAuth(); void AuthenticateClean( const WebApiAuthScopes& scopes, std::function onResponseEnd ); void AuthenticateClean_Cleanup(); void UpdateRefreshToken( abort_callback& abort ); private: void UpdateRefreshToken_NonBlocking( abort_callback& abort ); pplx::task CompleteAuthentication( const std::wstring& respondUrl ); void StartResponseListener( std::function onResponseEnd ); void StopResponseListener(); void HandleAuthenticationResponse( const web::http::http_response& response ); private: AbortManager& abortManager_; pplx::cancellation_token_source cts_; web::http::client::http_client client_; std::unique_ptr pListener_; std::wstring codeVerifier_; std::wstring state_; mutable std::mutex accessTokenMutex_; std::unique_ptr pAuthData_; }; } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_auth_scopes.cpp ================================================ #include #include "webapi_auth_scopes.h" #include using namespace std::literals::string_view_literals; namespace sptf { WebApiAuthScopes::WebApiAuthScopes( nonstd::span scopes ) { std::unordered_set scopeSet( scopes.cbegin(), scopes.cend() ); #define SPTF_FILL_SCOPE( scope_var, scope_id ) \ if ( scopeSet.count( scope_id ) ) \ { \ scope_var = true; \ } SPTF_FILL_SCOPE( playlist_modify_public, L"playlist-modify-public"sv ) SPTF_FILL_SCOPE( playlist_read_private, L"playlist-read-private"sv ) SPTF_FILL_SCOPE( playlist_modify_private, L"playlist-modify-private"sv ) SPTF_FILL_SCOPE( playlist_read_collaborative, L"playlist-read-collaborative"sv ) SPTF_FILL_SCOPE( user_read_playback_position, L"user-read-playback-position"sv ) SPTF_FILL_SCOPE( user_read_recently_played, L"user-read-recently-played"sv ) SPTF_FILL_SCOPE( user_top_read, L"user-top-read"sv ) SPTF_FILL_SCOPE( user_follow_modify, L"user-follow-modify"sv ) SPTF_FILL_SCOPE( user_follow_read, L"user-follow-read"sv ) SPTF_FILL_SCOPE( user_library_read, L"user-library-read"sv ) SPTF_FILL_SCOPE( user_library_modify, L"user-library-modify"sv ) SPTF_FILL_SCOPE( user_read_private, L"user-read-private"sv ) SPTF_FILL_SCOPE( user_read_email, L"user-read-email"sv ) #undef SPTF_FILL_SCOPE } std::wstring WebApiAuthScopes::ToWebString() const { std::wstring ret; #define SPTF_FILL_SCOPE( scope_var, scope_id ) \ if ( scope_var ) \ { \ ret += scope_id; \ ret += L" "; \ } SPTF_FILL_SCOPE( playlist_modify_public, L"playlist-modify-public"sv ) SPTF_FILL_SCOPE( playlist_read_private, L"playlist-read-private"sv ) SPTF_FILL_SCOPE( playlist_modify_private, L"playlist-modify-private"sv ) SPTF_FILL_SCOPE( playlist_read_collaborative, L"playlist-read-collaborative"sv ) SPTF_FILL_SCOPE( user_read_playback_position, L"user-read-playback-position"sv ) SPTF_FILL_SCOPE( user_read_recently_played, L"user-read-recently-played"sv ) SPTF_FILL_SCOPE( user_top_read, L"user-top-read"sv ) SPTF_FILL_SCOPE( user_follow_modify, L"user-follow-modify"sv ) SPTF_FILL_SCOPE( user_follow_read, L"user-follow-read"sv ) SPTF_FILL_SCOPE( user_library_read, L"user-library-read"sv ) SPTF_FILL_SCOPE( user_library_modify, L"user-library-modify"sv ) SPTF_FILL_SCOPE( user_read_private, L"user-read-private"sv ) SPTF_FILL_SCOPE( user_read_email, L"user-read-email"sv ) #undef SPTF_FILL_SCOPE return ret; } void to_json( nlohmann::json& j, const WebApiAuthScopes& p ) { #define SPTF_FILL_SCOPE( scope_var ) \ if ( p.scope_var ) \ { \ j[#scope_var] = p.scope_var; \ } SPTF_FILL_SCOPE( playlist_modify_public ) SPTF_FILL_SCOPE( playlist_read_private ) SPTF_FILL_SCOPE( playlist_modify_private ) SPTF_FILL_SCOPE( playlist_read_collaborative ) SPTF_FILL_SCOPE( user_read_playback_position ) SPTF_FILL_SCOPE( user_read_recently_played ) SPTF_FILL_SCOPE( user_top_read ) SPTF_FILL_SCOPE( user_follow_modify ) SPTF_FILL_SCOPE( user_follow_read ) SPTF_FILL_SCOPE( user_library_read ) SPTF_FILL_SCOPE( user_library_modify ) SPTF_FILL_SCOPE( user_read_private ) SPTF_FILL_SCOPE( user_read_email ) #undef SPTF_FILL_SCOPE } void from_json( const nlohmann::json& j, WebApiAuthScopes& p ) { #define SPTF_FILL_SCOPE( scope_var ) \ if ( j.contains( #scope_var ) ) \ { \ j[#scope_var].get_to( p.scope_var ); \ } SPTF_FILL_SCOPE( playlist_modify_public ) SPTF_FILL_SCOPE( playlist_read_private ) SPTF_FILL_SCOPE( playlist_modify_private ) SPTF_FILL_SCOPE( playlist_read_collaborative ) SPTF_FILL_SCOPE( user_read_playback_position ) SPTF_FILL_SCOPE( user_read_recently_played ) SPTF_FILL_SCOPE( user_top_read ) SPTF_FILL_SCOPE( user_follow_modify ) SPTF_FILL_SCOPE( user_follow_read ) SPTF_FILL_SCOPE( user_library_read ) SPTF_FILL_SCOPE( user_library_modify ) SPTF_FILL_SCOPE( user_read_private ) SPTF_FILL_SCOPE( user_read_email ) #undef SPTF_FILL_SCOPE } } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_auth_scopes.h ================================================ #pragma once #include #include namespace sptf { struct WebApiAuthScopes { WebApiAuthScopes() = default; WebApiAuthScopes( nonstd::span scopes ); std::wstring ToWebString() const; bool playlist_modify_public = false; bool playlist_read_private = false; bool playlist_modify_private = false; bool playlist_read_collaborative = false; bool user_read_playback_position = false; bool user_read_recently_played = false; bool user_top_read = false; bool user_follow_modify = false; bool user_follow_read = false; bool user_library_read = false; bool user_library_modify = false; bool user_read_private = false; bool user_read_email = false; }; void to_json( nlohmann::json& j, const WebApiAuthScopes& p ); void from_json( const nlohmann::json& j, WebApiAuthScopes& p ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_backend.cpp ================================================ #include #include "webapi_backend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO: replace unique_ptr with shared_ptr wherever needed to avoid copying namespace fs = std::filesystem; namespace { constexpr size_t kRpsLimit = 2; } namespace sptf { WebApi_Backend::WebApi_Backend( AbortManager& abortManager ) : abortManager_( abortManager ) , shouldLogWebApiRequest_( config::advanced::logging_webapi_request ) , shouldLogWebApiResponse_( config::advanced::logging_webapi_response ) , rpsLimiter_( kRpsLimit ) , client_( url::spotifyApi, GetClientConfig() ) , trackCache_( "tracks" ) , artistCache_( "artists" ) , albumImageCache_( "albums" ) , artistImageCache_( "artists" ) , pAuth_( std::make_unique( GetClientConfig(), abortManager ) ) { } WebApi_Backend::~WebApi_Backend() { } void WebApi_Backend::Finalize() { cts_.cancel(); pAuth_.reset(); } WebApiAuthorizer& WebApi_Backend::GetAuthorizer() { return *pAuth_; } std::unique_ptr WebApi_Backend::GetUser( abort_callback& abort ) { if ( auto userOpt = userCache_.GetObjectFromCache(); userOpt ) { return std::unique_ptr( std::move( *userOpt ) ); } else { web::uri_builder builder; builder.append_path( L"me" ); const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); auto ret = responseJson.get>(); userCache_.CacheObject( *ret ); return std::unique_ptr( std::move( ret ) ); } } void WebApi_Backend::RefreshCacheForTracks( nonstd::span trackIds, abort_callback& abort ) { constexpr size_t kMaxItemsPerRequest = 50; // remove duplicates const auto uniqueIds = trackIds | ranges::to>; for ( const auto& trackIdsChunk: uniqueIds | ranges::views::remove_if( [&]( const auto& id ) { return trackCache_.IsCached( id ); } ) | ranges::views::chunk( kMaxItemsPerRequest ) ) { const auto trackIdsStr = qwr::unicode::ToWide( qwr::string::Join( trackIdsChunk | ranges::to_vector, ',' ) ); web::uri_builder builder; builder .append_path( L"tracks" ) .append_query( L"ids", trackIdsStr ); const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); const auto tracksIt = responseJson.find( "tracks" ); qwr::QwrException::ExpectTrue( responseJson.cend() != tracksIt, L"Malformed track data response response: missing `tracks`" ); auto ret = tracksIt->get>>(); trackCache_.CacheObjects( ret ); } } std::unique_ptr WebApi_Backend::GetTrack( const std::string& trackId, abort_callback& abort, bool useRelink ) { // don't want to cache relinked tracks if ( auto trackOpt = trackCache_.GetObjectFromCache( trackId ); !useRelink && trackOpt ) { return std::unique_ptr( std::move( *trackOpt ) ); } else { web::uri_builder builder; builder .append_path( L"tracks" ) .append_path( qwr::unicode::ToWide( trackId ) ); if ( useRelink ) { if ( const auto countryOpt = GetUser( abort )->country; countryOpt ) { builder.append_query( L"market", qwr::unicode::ToWide( *countryOpt ) ); } } const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); auto ret = responseJson.get>(); if ( !useRelink ) { trackCache_.CacheObject( *ret ); } return std::unique_ptr( std::move( ret ) ); } } std::vector> WebApi_Backend::GetTracks( nonstd::span trackIds, abort_callback& abort ) { RefreshCacheForTracks( trackIds, abort ); return trackIds | ranges::views::transform( [&]( const auto& id ) -> std::unique_ptr { assert( trackCache_.IsCached( id ) ); return std::move( *trackCache_.GetObjectFromCache( id ) ); } ) | ranges::to_vector; } std::tuple< std::vector>, std::vector>> WebApi_Backend::GetTracksFromPlaylist( const std::string& playlistId, abort_callback& abort ) { constexpr size_t kMaxItemsPerRequest = 100; auto requestUri = [&] { web::uri_builder builder; builder .append_path( fmt::format( L"playlists/{}/tracks", qwr::unicode::ToWide( playlistId ) ) ) .append_query( L"limit", kMaxItemsPerRequest, false ); return builder.to_uri(); }(); std::vector> tracks; std::vector> localTracks; while ( true ) { const auto responseJson = GetJsonResponse( requestUri, abort ); const auto pPagingObject = responseJson.get>(); auto playlistTracks = pPagingObject->items.get>>(); for ( auto& playlistTrack: playlistTracks ) { std::visit( [&]( auto&& arg ) { using T = std::decay_t; if constexpr ( std::is_same_v ) { tracks.emplace_back( std::make_unique( std::move( arg ) ) ); } else if constexpr ( std::is_same_v ) { localTracks.emplace_back( std::make_unique( std::move( arg ) ) ); } else { static_assert( qwr::always_false_v, "non-exhaustive visitor!" ); } }, *playlistTrack->track ); } if ( !pPagingObject->next ) { break; } requestUri = *pPagingObject->next; } trackCache_.CacheObjects( tracks ); return { std::move( tracks ), std::move( localTracks ) }; } std::vector> WebApi_Backend::GetTracksFromAlbum( const std::string& albumId, abort_callback& abort ) { std::shared_ptr album; web::uri requestUri; std::vector> ret; while ( true ) { const auto responseJson = [&] { if ( requestUri.is_empty() ) { // first paging object (and uri) is retrieved from album web::uri_builder builder; builder.append_path( fmt::format( L"albums/{}", qwr::unicode::ToWide( albumId ) ) ); const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); responseJson.get_to( album ); const auto tracksIt = responseJson.find( "tracks" ); qwr::QwrException::ExpectTrue( responseJson.cend() != tracksIt, L"Malformed track data response: missing `tracks`" ); return *tracksIt; } else { return GetJsonResponse( requestUri, abort ); } }(); const auto pPagingObject = responseJson.get>(); auto newData = pPagingObject->items.get>>(); ret.insert( ret.end(), make_move_iterator( newData.begin() ), make_move_iterator( newData.end() ) ); if ( !pPagingObject->next ) { break; } requestUri = *pPagingObject->next; } auto newRet = ranges::views::transform( ret, [&]( auto&& elem ) { return std::make_unique( std::move( elem ), album ); } ) | ranges::to_vector; trackCache_.CacheObjects( newRet ); return newRet; } std::vector> WebApi_Backend::GetTopTracksForArtist( const std::string& artistId, abort_callback& abort ) { const auto countryOpt = GetUser( abort )->country; qwr::QwrException::ExpectTrue( countryOpt.has_value(), "Adding artist top tracks requires `user-read-private` permission.\n" "Re-login to update your permission scope." ); web::uri_builder builder; builder .append_path( L"artists" ) .append_path( qwr::unicode::ToWide( artistId ) ) .append_path( L"top-tracks" ) .append_query( L"market", qwr::unicode::ToWide( *countryOpt ) ); const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); const auto tracksIt = responseJson.find( "tracks" ); qwr::QwrException::ExpectTrue( responseJson.cend() != tracksIt, L"Malformed track data response response: missing `tracks`" ); auto ret = tracksIt->get>>(); trackCache_.CacheObjects( ret ); return ret; } std::vector> WebApi_Backend::GetMetaForTracks( nonstd::span> tracks ) { std::vector> ret; for ( const auto& track: tracks ) { auto& curMap = ret.emplace_back(); // This length will be overriden during playback curMap.emplace( "SPTF_LENGTH", fmt::format( "{}", track->duration_ms ) ); curMap.emplace( "TITLE", track->name ); curMap.emplace( "TRACKNUMBER", fmt::format( "{}", track->track_number ) ); curMap.emplace( "DISCNUMBER", fmt::format( "{}", track->disc_number ) ); for ( const auto& artist: track->artists ) { curMap.emplace( "ARTIST", artist->name ); } const auto& album = track->album; curMap.emplace( "ALBUM", album->name ); curMap.emplace( "DATE", album->release_date ); for ( const auto& artist: album->artists ) { curMap.emplace( "ALBUM ARTIST", artist->name ); } } // TODO: cache data ( might be useful) // TODO: check webapi_objects for other fields return ret; } void WebApi_Backend::RefreshCacheForArtists( nonstd::span artistIds, abort_callback& abort ) { // remove duplicates const auto uniqueIds = artistIds | ranges::to>; for ( const auto& idsChunk: uniqueIds | ranges::views::remove_if( [&]( const auto& id ) { return artistCache_.IsCached( id ); } ) | ranges::views::chunk( 50 ) ) { const auto idsStr = qwr::unicode::ToWide( qwr::string::Join( idsChunk | ranges::to_vector, ',' ) ); web::uri_builder builder; builder .append_path( L"artists" ) .append_query( L"ids", idsStr ); const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); const auto artistsIt = responseJson.find( "artists" ); qwr::QwrException::ExpectTrue( responseJson.cend() != artistsIt, L"Malformed track data response response: missing `artists`" ); auto ret = artistsIt->get>>(); artistCache_.CacheObjects( ret ); } } std::unique_ptr WebApi_Backend::GetArtist( const std::string& artistId, abort_callback& abort ) { if ( auto objectOpt = artistCache_.GetObjectFromCache( artistId ); objectOpt ) { return std::unique_ptr( std::move( *objectOpt ) ); } else { web::uri_builder builder; builder .append_path( L"artists" ) .append_path( qwr::unicode::ToWide( artistId ) ); const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); auto ret = responseJson.get>(); artistCache_.CacheObject( *ret ); return std::unique_ptr( std::move( ret ) ); } } fs::path WebApi_Backend::GetAlbumImage( const std::string& albumId, const std::string& imgUrl, abort_callback& abort ) { return albumImageCache_.GetImage( albumId, imgUrl, abort ); } fs::path WebApi_Backend::GetArtistImage( const std::string& artistId, const std::string& imgUrl, abort_callback& abort ) { return artistImageCache_.GetImage( artistId, imgUrl, abort ); } web::http::client::http_client_config WebApi_Backend::GetClientConfig() { const auto proxyUrl = qwr::unicode::ToWide( sptf::config::advanced::network_proxy.GetValue() ); const auto proxyUsername = qwr::unicode::ToWide( sptf::config::advanced::network_proxy_username.GetValue() ); const auto proxyPassword = qwr::unicode::ToWide( sptf::config::advanced::network_proxy_password.GetValue() ); web::http::client::http_client_config config; if ( !proxyUrl.empty() ) { web::web_proxy proxy( web::uri::encode_uri( proxyUrl ) ); if ( !proxyUsername.empty() && !proxyPassword.empty() ) { proxy.set_credentials( web::credentials{ proxyUsername, proxyPassword } ); } config.set_proxy( std::move( proxy ) ); } return config; } nlohmann::json WebApi_Backend::GetJsonResponse( const web::uri& requestUri, abort_callback& abort ) { return ParseResponse( GetResponse( requestUri, abort ) ); } web::http::http_response WebApi_Backend::GetResponse( const web::uri& requestUri, abort_callback& abort ) { const auto adjustedRequestUri = [&] { const auto uriStr = requestUri.to_string(); const auto baseUriStr = client_.base_uri().to_string(); if ( uriStr._Starts_with( baseUriStr ) ) { return web::uri{ uriStr.data() + baseUriStr.size() }; } else { return requestUri; } }(); if ( shouldLogWebApiRequest_ ) { FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (debug): request:\n" << qwr::unicode::ToU8( adjustedRequestUri.to_string() ); } web::http::http_request req( web::http::methods::GET ); req.headers().add( L"Authorization", fmt::format( L"Bearer {}", pAuth_->GetAccessToken( abort ) ) ); req.headers().add( L"Accept", L"application/json" ); req.headers().set_content_type( L"application/json" ); req.set_request_uri( adjustedRequestUri ); rpsLimiter_.WaitForRequestAvailability( abort ); qwr::QwrException::ExpectTrue( !abort.is_aborting(), "Abort was signaled, canceling request..." ); auto ctsToken = cts_.get_token(); auto localCts = Concurrency::cancellation_token_source::create_linked_source( ctsToken ); const auto abortableScope = abortManager_.GetAbortableScope( [&localCts] { localCts.cancel(); }, abort ); web::http::http_response response; for ( size_t i = 0; i < 3; ++i ) { response = client_.request( req, localCts.get_token() ).get(); if ( response.status_code() != 429 ) { break; } const auto it = response.headers().find( L"Retry-After" ); qwr::QwrException::ExpectTrue( it != response.headers().end(), "Request failed with 429 error, but does not contain a `Retry-After` header" ); const auto& [_, retryHeader] = *it; const auto retryInMsOpt = qwr::string::GetNumber( qwr::unicode::ToU8( retryHeader ) ); qwr::QwrException::ExpectTrue( retryInMsOpt.has_value(), "Request failed with 429 error, but does not contain a valid number in `Retry-After` header" ); const auto retryIn = std::chrono::milliseconds( *retryInMsOpt ) + std::chrono::seconds( 1 ); FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (error):\n" << fmt::format( L"Rate limit reached: retrying in {} ms", retryIn.count() ); if ( !SleepFor( retryIn, abort ) ) { break; } } if ( response.status_code() == 429 ) { FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (error):\n" << fmt::format( L"Rate limit reached: retry failed" ); } return response; } nlohmann::json WebApi_Backend::ParseResponse( const web::http::http_response& response ) { if ( response.status_code() != 200 ) { throw qwr::QwrException( L"{}: {}\n" L"Additional data: {}\n", (int)response.status_code(), response.reason_phrase(), [&]() -> std::wstring { try { const auto responseJson = nlohmann::json::parse( response.extract_string().get() ); return qwr::unicode::ToWide( responseJson.dump( 2 ) ); } catch ( ... ) { return response.to_string(); } }() ); } const auto responseJson = nlohmann::json::parse( response.extract_string().get() ); if ( shouldLogWebApiResponse_ ) { FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (debug): response:\n" << responseJson.dump( 2 ); } qwr::QwrException::ExpectTrue( responseJson.is_object(), L"Malformed track data response response: json is not an object" ); return responseJson; } } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_backend.h ================================================ #pragma once #include #include #include #include #include #include #include #include namespace sptf { struct WebApi_User; struct WebApi_Track; struct WebApi_LocalTrack; struct WebApi_Artist; class WebApiAuthorizer; class AbortManager; class WebApi_Backend { public: WebApi_Backend( AbortManager& abortManager ); ~WebApi_Backend(); void Finalize(); WebApiAuthorizer& GetAuthorizer(); std::unique_ptr GetUser( abort_callback& abort ); void RefreshCacheForTracks( nonstd::span trackIds, abort_callback& abort ); std::unique_ptr GetTrack( const std::string& trackId, abort_callback& abort, bool useRelink = false ); std::vector> GetTracks( nonstd::span trackIds, abort_callback& abort ); std::tuple< std::vector>, std::vector>> GetTracksFromPlaylist( const std::string& playlistId, abort_callback& abort ); std::vector> GetTracksFromAlbum( const std::string& albumId, abort_callback& abort ); std::vector> GetTopTracksForArtist( const std::string& artistId, abort_callback& abort ); std::vector> GetMetaForTracks( nonstd::span> tracks ); void RefreshCacheForArtists( nonstd::span artistIds, abort_callback& abort ); std::unique_ptr GetArtist( const std::string& artistId, abort_callback& abort ); std::filesystem::path GetAlbumImage( const std::string& albumId, const std::string& imgUrl, abort_callback& abort ); std::filesystem::path GetArtistImage( const std::string& artistId, const std::string& imgUrl, abort_callback& abort ); private: static web::http::client::http_client_config GetClientConfig(); nlohmann::json GetJsonResponse( const web::uri& requestUri, abort_callback& abort ); web::http::http_response GetResponse( const web::uri& requestUri, abort_callback& abort ); nlohmann::json ParseResponse( const web::http::http_response& response ); private: AbortManager& abortManager_; RpsLimiter rpsLimiter_; bool shouldLogWebApiRequest_ = false; bool shouldLogWebApiResponse_ = false; pplx::cancellation_token_source cts_; std::unique_ptr pAuth_; web::http::client::http_client client_; WebApi_UserCache userCache_; WebApi_ObjectCache trackCache_; WebApi_ObjectCache artistCache_; WebApi_ImageCache albumImageCache_; WebApi_ImageCache artistImageCache_; }; } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_cache.cpp ================================================ #include #include "webapi_cache.h" #include #include #include #include #include // RODO: move to props #pragma comment( lib, "Urlmon.lib" ) namespace fs = std::filesystem; namespace { using namespace sptf; class DownloadStatus : public IBindStatusCallback { public: DownloadStatus( abort_callback& abort ); public: STDMETHOD( OnStartBinding )( /* [in] */ DWORD dwReserved, /* [in] */ IBinding __RPC_FAR* pib ) { return E_NOTIMPL; } STDMETHOD( GetPriority )( /* [out] */ LONG __RPC_FAR* pnPriority ) { return E_NOTIMPL; } STDMETHOD( OnLowResource )( /* [in] */ DWORD reserved ) { return E_NOTIMPL; } STDMETHOD( OnProgress )( /* [in] */ ULONG ulProgress, /* [in] */ ULONG ulProgressMax, /* [in] */ ULONG ulStatusCode, /* [in] */ LPCWSTR wszStatusText ); STDMETHOD( OnStopBinding )( /* [in] */ HRESULT hresult, /* [unique][in] */ LPCWSTR szError ) { return E_NOTIMPL; } STDMETHOD( GetBindInfo )( /* [out] */ DWORD __RPC_FAR* grfBINDF, /* [unique][out][in] */ BINDINFO __RPC_FAR* pbindinfo ) { return E_NOTIMPL; } STDMETHOD( OnDataAvailable )( /* [in] */ DWORD grfBSCF, /* [in] */ DWORD dwSize, /* [in] */ FORMATETC __RPC_FAR* pformatetc, /* [in] */ STGMEDIUM __RPC_FAR* pstgmed ) { return E_NOTIMPL; } STDMETHOD( OnObjectAvailable )( /* [in] */ REFIID riid, /* [iid_is][in] */ IUnknown __RPC_FAR* punk ) { return E_NOTIMPL; } // IUnknown methods. Note that IE never calls any of these methods, since // the caller owns the IBindStatusCallback interface, so the methods all // return zero/E_NOTIMPL. STDMETHOD_( ULONG, AddRef ) () { return 0; } STDMETHOD_( ULONG, Release ) () { return 0; } STDMETHOD( QueryInterface )( /* [in] */ REFIID riid, /* [iid_is][out] */ void __RPC_FAR* __RPC_FAR* ppvObject ) { return E_NOTIMPL; } private: std::unique_ptr pScope_; std::mutex mutex_; bool hasAborted_ = false; }; DownloadStatus::DownloadStatus( abort_callback& abort ) { auto& am = SpotifyInstance::Get().GetAbortManager(); pScope_ = std::make_unique( am.GetAbortableScope( [&] { std::lock_guard lg( mutex_ ); hasAborted_ = true; }, abort ) ); } HRESULT DownloadStatus::OnProgress( ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR wszStatusText ) { std::lock_guard lg( mutex_ ); return ( hasAborted_ ? E_ABORT : S_OK ); } } // namespace namespace sptf { WebApi_UserCache::WebApi_UserCache() : jsonCache_( "" ) { } void WebApi_UserCache::CacheObject( const WebApi_User& object, bool force /*= false */ ) { std::lock_guard lock( cacheMutex_ ); jsonCache_.CacheObject_NonBlocking( object, "me", force ); } std::optional> WebApi_UserCache::GetObjectFromCache() { std::lock_guard lock( cacheMutex_ ); return jsonCache_.GetObjectFromCache_NonBlocking( "me" ); } WebApi_ImageCache::WebApi_ImageCache( const std::string& cacheSubdir ) : cacheSubdir_( cacheSubdir ) { } fs::path WebApi_ImageCache::GetImage( const std::string& id, const std::string& imgUrl, abort_callback& abort ) { std::lock_guard lock( cacheMutex_ ); const auto imagePath = path::WebApiCache() / "images" / cacheSubdir_ / fmt::format( "{}.jpeg", id ); if ( !fs::exists( imagePath ) ) { fs::create_directories( imagePath.parent_path() ); const auto url_w = qwr::unicode::ToWide( imgUrl ); DownloadStatus ds( abort ); auto hr = URLDownloadToFile( nullptr, url_w.c_str(), imagePath.c_str(), 0, &ds ); if ( FAILED( hr ) ) { fs::remove( imagePath ); // in case download was aborted midway qwr::error::CheckHR( hr, "URLDownloadToFile" ); } } assert( fs::exists( imagePath ) ); return imagePath; } } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_cache.h ================================================ #pragma once #include #include #include #include #include namespace sptf { template class WebApi_JsonCache { public: WebApi_JsonCache( const std::string& cacheSubdir ) : cacheSubdir_( cacheSubdir ) { } std::optional> GetObjectFromCache_NonBlocking( const std::string& filename ) { namespace fs = std::filesystem; const auto filePath = GetCachedPath( filename ); if ( !fs::exists( filePath ) ) { return std::nullopt; } const auto data = qwr::file::ReadFile( filePath, CP_UTF8, false ); try { return nlohmann::json::parse( data ).get>(); } catch ( const nlohmann::detail::exception& ) { return std::nullopt; } } void CacheObject_NonBlocking( const T& object, const std::string& filename, bool force ) { namespace fs = std::filesystem; const auto filePath = GetCachedPath( filename ); if ( fs::exists( filePath ) ) { if ( !force ) { return; } fs::remove( filePath ); } fs::create_directories( filePath.parent_path() ); qwr::file::WriteFile( filePath, nlohmann::json( object ).dump( 2 ) ); } bool IsCached_NonBlocking( const std::string& filename ) { namespace fs = std::filesystem; const auto filePath = GetCachedPath( filename ); return fs::exists( filePath ); } private: std::filesystem::path GetCachedPath( const std::string& filename ) const { return path::WebApiCache() / "data" / cacheSubdir_ / fmt::format( "{}.json", filename ); } private: std::string cacheSubdir_; }; template class WebApi_ObjectCache { public: WebApi_ObjectCache( const std::string& cacheSubdir ) : jsonCache_( cacheSubdir ) { } void CacheObject( const T& object, bool force = false ) { std::lock_guard lock( cacheMutex_ ); jsonCache_.CacheObject_NonBlocking( object, object.id, force ); } void CacheObjects( const nonstd::span> objects, bool force = false ) { std::lock_guard lock( cacheMutex_ ); for ( const auto& pObject: objects ) { jsonCache_.CacheObject_NonBlocking( *pObject, pObject->id, force ); } } void CacheObjects( const nonstd::span> objects, bool force = false ) { std::lock_guard lock( cacheMutex_ ); for ( const auto& pObject: objects ) { jsonCache_.CacheObject_NonBlocking( *pObject, pObject->id, force ); } } std::optional> GetObjectFromCache( const std::string& id ) { std::lock_guard lock( cacheMutex_ ); return jsonCache_.GetObjectFromCache_NonBlocking( id ); } bool IsCached( const std::string& id ) { std::lock_guard lock( cacheMutex_ ); return jsonCache_.IsCached_NonBlocking( id ); } private: std::mutex cacheMutex_; WebApi_JsonCache jsonCache_; }; struct WebApi_User; class WebApi_UserCache { public: WebApi_UserCache(); void CacheObject( const WebApi_User& object, bool force = false ); std::optional> GetObjectFromCache(); private: std::mutex cacheMutex_; WebApi_JsonCache jsonCache_; }; class WebApi_ImageCache { public: WebApi_ImageCache( const std::string& cacheSubdir ); std::filesystem::path GetImage( const std::string& id, const std::string& imgUrl, abort_callback& abort ); private: std::string cacheSubdir_; std::mutex cacheMutex_; }; } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_album.cpp ================================================ #include #include "webapi_album.h" #include #include namespace sptf { SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Album_Simplified, artists, images, release_date, name, id ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_album.h ================================================ #pragma once #include #include #include namespace sptf { struct WebApi_Artist_Simplified; struct WebApi_Image; struct WebApi_Album_Simplified { // album_group string, optional The field is present when getting an artists albums. Possible values are album, single, compilation, appears_on. Compare to album_type this field represents relationship between the artist and the album. // album_type string The type of the album: one of album, single, or compilation. // available_markets array of strings The markets in which the album is available: ISO 3166-1 alpha-2 country codes. Note that an album is considered available in a market when at least 1 of its tracks is available in that market. // external_urls an external URL object Known external URLs for this album. // release_date_precision string The precision with which release_date value is known: year , month , or day. // restrictions a restrictions object Part of the response when Track Relinking is applied, the original track is not available in the given market, and Spotify did not have any tracks to relink it with. The track response will still contain metadata for the original track, and a restrictions object containing the reason why the track is not available: "restrictions" : {"reason" : "market"} std::vector> artists; std::vector> images; std::string id; std::string release_date; std::string name; }; void to_json( nlohmann::json& j, const WebApi_Album_Simplified& p ); void from_json( const nlohmann::json& j, WebApi_Album_Simplified& p ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_artist.cpp ================================================ #include #include "webapi_artist.h" #include #include namespace sptf { SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Artist_Simplified, id, name ); SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Artist, id, images, name, popularity ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_artist.h ================================================ #pragma once #include namespace sptf { struct WebApi_Image; struct WebApi_Artist_Simplified { // external_urls an external URL object Known external URLs for this artist. std::string id; std::string name; }; struct WebApi_Artist { // external_urls an external URL object Known external URLs for this artist. // followers A followers object Information about the followers of the artist. // genres array of strings A list of the genres the artist is associated with. For example: "Prog Rock" , "Post-Grunge". (If not yet classified, the array is empty.) std::string id; std::vector> images; std::string name; uint32_t popularity; }; void to_json( nlohmann::json& j, const WebApi_Artist_Simplified& p ); void from_json( const nlohmann::json& j, WebApi_Artist_Simplified& p ); void to_json( nlohmann::json& j, const WebApi_Artist& p ); void from_json( const nlohmann::json& j, WebApi_Artist& p ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_image.cpp ================================================ #include #include "webapi_image.h" namespace sptf { SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Image, height, url, width ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_image.h ================================================ #pragma once #include #include namespace sptf { struct WebApi_Image { uint32_t height; std::string url; uint32_t width; }; void to_json( nlohmann::json& j, const WebApi_Image& p ); void from_json( const nlohmann::json& j, WebApi_Image& p ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_media_objects.h ================================================ #pragma once #include #include #include #include #include #include #include ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_paging_object.cpp ================================================ #include #include "webapi_paging_object.h" #include namespace sptf { void from_json( const nlohmann::json& nlohmann_json_j, WebApi_PagingObject& nlohmann_json_t ) { NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_FROM, items, limit, next, offset, previous, total ) ) } } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_paging_object.h ================================================ #pragma once #include #include #include namespace sptf { struct WebApi_PagingObject { // href string A link to the Web API endpoint returning the full result of the request. nlohmann::json items; size_t limit; std::optional next; size_t offset; std::optional previous; size_t total; }; void from_json( const nlohmann::json& j, WebApi_PagingObject& p ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_playlist_track.cpp ================================================ #include #include "webapi_playlist_track.h" #include #include namespace sptf { void to_json( nlohmann::json& j, const WebApi_PlaylistTrack& p ) { std::visit( [&j]( auto&& arg ) { j["track"] = arg; }, *p.track ); } void from_json( const nlohmann::json& j, WebApi_PlaylistTrack& p ) { if ( j.at( "is_local" ).get() ) { p.track = std::make_unique>( j.at( "track" ).get() ); } else { p.track = std::make_unique>( j.at( "track" ).get() ); } } SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_LocalTrack, uri, name ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_playlist_track.h ================================================ #pragma once #include #include #include namespace sptf { struct WebApi_Track; struct WebApi_LocalTrack { std::string uri; std::optional name; }; struct WebApi_PlaylistTrack { // added_at a timestamp The date and time the track was added. Note that some very old playlists may return null in this field. // added_by a user object The Spotify user who added the track. Note that some very old playlists may return null in this field. std::unique_ptr> track; }; void to_json( nlohmann::json& j, const WebApi_PlaylistTrack& p ); void from_json( const nlohmann::json& j, WebApi_PlaylistTrack& p ); void to_json( nlohmann::json& j, const WebApi_LocalTrack& p ); void from_json( const nlohmann::json& j, WebApi_LocalTrack& p ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_restriction.cpp ================================================ #include #include "webapi_restriction.h" namespace sptf { SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Restriction, reason ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_restriction.h ================================================ #pragma once #include #include namespace sptf { struct WebApi_Restriction { std::string reason; }; void to_json( nlohmann::json& j, const WebApi_Restriction& p ); void from_json( const nlohmann::json& j, WebApi_Restriction& p ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_track.cpp ================================================ #include #include "webapi_track.h" #include #include namespace sptf { WebApi_Track::WebApi_Track( std::unique_ptr trackSimplified, std::shared_ptr albumSimplified ) { album = albumSimplified; artists = std::move( trackSimplified->artists ); disc_number = trackSimplified->disc_number; duration_ms = trackSimplified->duration_ms; name = trackSimplified->name; linked_from = std::move( trackSimplified->linked_from ); restrictions = std::move( trackSimplified->restrictions ); preview_url = trackSimplified->preview_url; track_number = trackSimplified->track_number; id = trackSimplified->id; } void to_json( nlohmann::json& nlohmann_json_j, const WebApi_Track_Simplified& nlohmann_json_t ) { // we don't need to save `restrictions`, since it's only used on initial parsing NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_TO, artists, disc_number, duration_ms, linked_from, name, preview_url, track_number, id ) ) } void from_json( const nlohmann::json& nlohmann_json_j, WebApi_Track_Simplified& nlohmann_json_t ) { NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_FROM, artists, disc_number, duration_ms, name, preview_url, track_number, id ) ) if ( nlohmann_json_j.contains( "linked_from" ) ) { NLOHMANN_JSON_FROM( linked_from ) } if ( nlohmann_json_j.contains( "restrictions" ) ) { NLOHMANN_JSON_FROM( restrictions ) } } void to_json( nlohmann::json& nlohmann_json_j, const WebApi_Track& nlohmann_json_t ) { // we don't need to save `restrictions`, since it's only used on initial parsing NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_TO, album, artists, disc_number, duration_ms, linked_from, name, preview_url, track_number, id ) ) } void from_json( const nlohmann::json& nlohmann_json_j, WebApi_Track& nlohmann_json_t ) { NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_FROM, album, artists, disc_number, duration_ms, name, preview_url, track_number, id ) ) if ( nlohmann_json_j.contains( "linked_from" ) ) { NLOHMANN_JSON_FROM( linked_from ) } if ( nlohmann_json_j.contains( "restrictions" ) ) { NLOHMANN_JSON_FROM( restrictions ) } } } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_track.h ================================================ #pragma once #include #include #include namespace sptf { struct WebApi_Album_Simplified; struct WebApi_Artist_Simplified; struct WebApi_Restriction; struct WebApi_TrackLink; struct WebApi_Track_Simplified { // available_markets array of strings A list of the countries in which the track can be played, identified by their ISO 3166-1 alpha-2 code. // explicit Boolean Whether or not the track has explicit lyrics ( true = yes it does; false = no it does not OR unknown). // external_urls an external URL object External URLs for this track. // is_playable boolean Part of the response when Track Relinking is applied.If true, the track is playable in the given market.Otherwise false. // is_local boolean Whether or not the track is from a local file. std::vector> artists; uint32_t disc_number; uint32_t duration_ms; std::string id; std::optional> linked_from; std::optional> restrictions; std::string name; std::optional preview_url; uint32_t track_number; }; struct WebApi_Track { WebApi_Track() = default; WebApi_Track( std::unique_ptr trackSimplified, std::shared_ptr albumSimplified ); //available_markets array of strings A list of the countries in which the track can be played, identified by their ISO 3166-1 alpha-2 code. //explicit Boolean Whether or not the track has explicit lyrics ( true = yes it does; false = no it does not OR unknown). //external_ids an external ID object Known external IDs for the track. //external_urls an external URL object Known external URLs for this track. //is_playable boolean Part of the response when Track Relinking is applied.If true, the track is playable in the given market.Otherwise false. //popularity integer The popularity of the track. The value will be between 0 and 100, with 100 being the most popular. //The popularity of a track is a value between 0 and 100, with 100 being the most popular. The popularity is calculated by algorithm and is based, in the most part, on the total number of plays the track has had and how recent those plays are. //Generally speaking, songs that are being played a lot now will have a higher popularity than songs that were played a lot in the past. Duplicate tracks (e.g. the same track from a single and an album) are rated independently. Artist and album popularity is derived mathematically from track popularity. Note that the popularity value may lag actual popularity by a few days: the value is not updated in real time. //is_local boolean Whether or not the track is from a local file. std::shared_ptr album; std::vector> artists; uint32_t disc_number; uint32_t duration_ms; std::string id; std::optional> linked_from; std::optional> restrictions; std::string name; std::optional preview_url; uint32_t track_number; }; void to_json( nlohmann::json& j, const WebApi_Track_Simplified& p ); void from_json( const nlohmann::json& j, WebApi_Track_Simplified& p ); void to_json( nlohmann::json& j, const WebApi_Track& p ); void from_json( const nlohmann::json& j, WebApi_Track& p ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_track_link.cpp ================================================ #include #include "webapi_track_link.h" #include #include namespace sptf { SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_TrackLink, id ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_track_link.h ================================================ #pragma once #include #include namespace sptf { struct WebApi_TrackLink { // external_urls an external URL object Known external URLs for this track. // href string A link to the Web API endpoint providing full details of the track. std::string id; // type string The object type: track. // uri string The Spotify URI for the track. }; void to_json( nlohmann::json& j, const WebApi_TrackLink& p ); void from_json( const nlohmann::json& j, WebApi_TrackLink& p ); } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_user.cpp ================================================ #include #include "webapi_user.h" #include namespace sptf { void to_json( nlohmann::json& nlohmann_json_j, const WebApi_User& nlohmann_json_t ) { NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_TO, country, display_name, uri ) ) } void from_json( const nlohmann::json& nlohmann_json_j, WebApi_User& nlohmann_json_t ) { NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_FROM, display_name, uri ) ) if ( nlohmann_json_j.contains( "country" ) ) { NLOHMANN_JSON_FROM( country ) } } } // namespace sptf ================================================ FILE: foo_spotify/backend/webapi_objects/webapi_user.h ================================================ #pragma once #include #include namespace sptf { struct WebApi_User { std::optional country; std::optional display_name; //std::optional email; //external_urls an external URL object Known external URLs for this user. //followers A followers object Information about the followers of the user. //href string A link to the Web API endpoint for this user. std::string id; //images an array of image objects The users profile image. //product string The users Spotify subscription level: premium, free, etc. (The subscription level open can be considered the same as free.) This field is only available when the current user has granted access to the user-read-private scope. std::string uri; }; void to_json( nlohmann::json& j, const WebApi_User& p ); void from_json( const nlohmann::json& j, WebApi_User& p ); } // namespace sptf ================================================ FILE: foo_spotify/component_defines.h ================================================ #pragma once // Generated by scripts from setup.py #include #include #define SPTF_NAME "Spotify Integration" #define SPTF_UNDERSCORE_NAME "foo_spotify" #define SPTF_DLL_NAME SPTF_UNDERSCORE_NAME ".dll" #define SPTF_STRINGIFY_HELPER( x ) #x #define SPTF_STRINGIFY( x ) SPTF_STRINGIFY_HELPER( x ) #ifdef SPTF_VERSION_PRERELEASE_TEXT # define SPTF_VERSION_PRERELEASE "-" SPTF_VERSION_PRERELEASE_TEXT # define SPTF_VERSION_METADATA "+" SPTF_STRINGIFY( SPTF_COMMIT_HASH ) #else # define SPTF_VERSION_PRERELEASE "" # define SPTF_VERSION_METADATA "" #endif #ifdef _DEBUG # define SPTF_VERSION_DEBUG_SUFFIX " (Debug)" #else # define SPTF_VERSION_DEBUG_SUFFIX "" #endif #define SPTF_VERSION \ SPTF_STRINGIFY( SPTF_VERSION_MAJOR ) \ "." SPTF_STRINGIFY( SPTF_VERSION_MINOR ) "." SPTF_STRINGIFY( SPTF_VERSION_PATCH ) \ SPTF_VERSION_PRERELEASE SPTF_VERSION_METADATA #define SPTF_NAME_WITH_VERSION SPTF_NAME " v" SPTF_VERSION SPTF_VERSION_DEBUG_SUFFIX #define SPTF_ABOUT \ SPTF_NAME_WITH_VERSION " by TheQwertiest" ================================================ FILE: foo_spotify/component_guids.h ================================================ #pragma once namespace sptf::guid { constexpr GUID acfu_source = { 0xbfbd48bc, 0x9f3b, 0x42bd, { 0x8e, 0xfc, 0x9d, 0x5a, 0xf1, 0x2f, 0xf3, 0xa1 } }; constexpr GUID adv_branch = { 0x3e2d241a, 0x306b, 0x49bc, { 0x80, 0xb3, 0x6a, 0x77, 0xe9, 0x21, 0x32, 0xc7 } }; constexpr GUID adv_branch_logging = { 0xa69190a1, 0x3abd, 0x4a45, { 0x9c, 0x4a, 0x66, 0xbd, 0xb, 0x7f, 0xec, 0x11 } }; constexpr GUID adv_branch_network = { 0x53328c11, 0x156e, 0x4b5c, { 0x8f, 0x82, 0xe5, 0x3d, 0x5d, 0xb5, 0x7c, 0x2b } }; constexpr GUID adv_var_network_proxy = { 0x2626706b, 0x19a9, 0x4ccf, { 0x85, 0xdd, 0x55, 0xd4, 0x2f, 0x8b, 0x57, 0x46 } }; constexpr GUID adv_var_network_proxy_username = { 0xd9e86980, 0xcee4, 0x4075, { 0x96, 0xef, 0x79, 0xed, 0xba, 0x87, 0x79, 0x58 } }; constexpr GUID adv_var_network_proxy_password = { 0xd138fb5, 0x3e6f, 0x48d6, { 0x9b, 0x44, 0x44, 0x6c, 0x78, 0xd4, 0x6f, 0xa3 } }; constexpr GUID adv_var_logging_webapi_debug = { 0xea784339, 0x21d7, 0x47ab, { 0xbc, 0xeb, 0x7a, 0xf7, 0xc, 0x8f, 0xb0, 0x18 } }; constexpr GUID adv_var_logging_webapi_request = { 0x90066d1d, 0x1233, 0x4fcc, { 0xab, 0xc3, 0xbc, 0x17, 0xb4, 0x68, 0x65, 0x84 } }; constexpr GUID adv_var_logging_webapi_response = { 0x349d3d49, 0xfffc, 0x4b32, { 0x8b, 0xf7, 0xc0, 0x78, 0x3a, 0x87, 0x5e, 0xa4 } }; constexpr GUID config_enable_normalization = { 0x7917bfbc, 0x3731, 0x4523, { 0xa4, 0x9e, 0xf8, 0xb3, 0x8e, 0xad, 0xbd, 0xb1 } }; constexpr GUID config_enable_private_mode = { 0xfd7aad3c, 0x3e8f, 0x45c2, { 0xaa, 0x62, 0xbe, 0xcc, 0x55, 0xe1, 0x2d, 0xfb } }; constexpr GUID config_libspotify_cache_size_in_mb = { 0xf23f3e, 0x5d86, 0x4092, { 0x8d, 0xf, 0xf1, 0x7d, 0x5b, 0xa1, 0x22, 0xf7 } }; constexpr GUID config_libspotify_cache_size_in_percent = { 0xf2107600, 0x4e31, 0x4039, { 0x9a, 0x5, 0xd3, 0x8f, 0x23, 0x25, 0x88, 0x72 } }; constexpr GUID config_preferred_bitrate = { 0xfaf55f2c, 0xe6d6, 0x4adc, { 0xae, 0x85, 0x3e, 0xcc, 0x8c, 0x19, 0xa, 0xe0 } }; constexpr GUID config_webapi_auth_scopes = { 0xabc0e6b2, 0x2569, 0x42ee, { 0xb8, 0xb0, 0x86, 0x7e, 0x3a, 0xc3, 0x21, 0x9 } }; constexpr GUID input = { 0xb376ad64, 0xe029, 0x40f6, { 0x89, 0xf8, 0xae, 0xc, 0xfa, 0x73, 0xa5, 0xa3 } }; constexpr GUID preferences = { 0x7285bbbd, 0x2109, 0x437c, { 0x8f, 0x3e, 0xaa, 0xa9, 0xc9, 0x90, 0x65, 0x2c } }; } // namespace sptf::guid ================================================ FILE: foo_spotify/component_paths.cpp ================================================ #include #include "component_paths.h" #include namespace fs = std::filesystem; namespace sptf::path { fs::path LibSpotifyCache() { return qwr::path::Profile() / SPTF_UNDERSCORE_NAME / "ls" / "cache"; } fs::path LibSpotifySettings() { return qwr::path::Profile() / SPTF_UNDERSCORE_NAME / "ls" / "settings"; } fs::path WebApiCache() { return qwr::path::Profile() / SPTF_UNDERSCORE_NAME / "wa" / "cache"; } fs::path WebApiSettings() { return qwr::path::Profile() / SPTF_UNDERSCORE_NAME / "wa" / "settings"; } } // namespace sptf::path ================================================ FILE: foo_spotify/component_paths.h ================================================ #pragma once #include namespace sptf::path { std::filesystem::path LibSpotifyCache(); std::filesystem::path LibSpotifySettings(); std::filesystem::path WebApiCache(); std::filesystem::path WebApiSettings(); } // namespace sptf::guid ================================================ FILE: foo_spotify/component_urls.h ================================================ #pragma once namespace sptf::url { constexpr wchar_t accountsAuthenticate[] = L"https://accounts.spotify.com/authorize"; constexpr wchar_t accountsApi[] = L"https://accounts.spotify.com/api/token"; constexpr wchar_t spotifyApi[] = L"https://api.spotify.com/v1"; constexpr wchar_t redirectUri[] = L"http://localhost:23018"; constexpr wchar_t homepage[] = L"https://github.com/TheQwertiest/" SPTF_UNDERSCORE_NAME; } // namespace sptf::url ================================================ FILE: foo_spotify/dllmain.cpp ================================================ #include #include #include #include #include #include DECLARE_COMPONENT_VERSION( SPTF_NAME, SPTF_VERSION, SPTF_ABOUT ); VALIDATE_COMPONENT_FILENAME( SPTF_DLL_NAME ); using namespace sptf; namespace { bool HasComponent( const std::u8string& componentName ) { pfc::string8_fast temp; for ( service_enum_t e; !e.finished(); ++e ) { auto cv = e.get(); cv->get_file_name( temp ); if ( temp.c_str() == componentName ) { return true; } } return false; } } // namespace namespace { class sptf_initquit : public initquit { public: void on_init() override { qwr::DelayedExecutor::GetInstance().EnableExecution(); ///< Enable task processing (e.g. error popups) if ( HasComponent( "foo_input_spotify" ) ) { qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, "!!! `foo_input_spotify` component detected! !!!\n" "!!! It is incompatible with `foo_spotify` and must be removed! !!!\n" "!!! Otherwise foobar2000 *will* crash! !!!" ); } } void on_quit() override { // Careful when changing invocation order here! qwr::GlobalAbortCallback::GetInstance().Abort(); SpotifyInstance::Get().Finalize(); ui::NotAuthHandler::Get().CloseDialog(); } }; initquit_factory_t g_initquit; } // namespace extern "C" BOOL WINAPI DllMain( HINSTANCE ins, DWORD reason, [[maybe_unused]] LPVOID lp ) { switch ( reason ) { case DLL_PROCESS_ATTACH: { break; } case DLL_PROCESS_DETACH: { break; } default: break; } return TRUE; } ================================================ FILE: foo_spotify/fb2k/acfu_integration.cpp ================================================ #include #include #include using namespace sptf; namespace { class SptfSource : public qwr::acfu::QwrSource { public: // ::acfu::source GUID get_guid() override; ::acfu::request::ptr create_request() override; // qwr::acfu::github_conf static pfc::string8 get_owner(); static pfc::string8 get_repo(); // qwr::acfu::QwrSource std::string GetComponentName() const override; std::string GetComponentFilename() const override; }; } // namespace namespace { GUID SptfSource::get_guid() { return guid::acfu_source; } ::acfu::request::ptr SptfSource::create_request() { return fb2k::service_new>(); } pfc::string8 SptfSource::get_owner() { return "TheQwertiest"; } pfc::string8 SptfSource::get_repo() { return qwr::unicode::ToU8( std::wstring_view{ url::homepage } ).c_str(); } std::string SptfSource::GetComponentName() const { return SPTF_NAME; } std::string SptfSource::GetComponentFilename() const { return SPTF_UNDERSCORE_NAME; } service_factory_single_t g_acfuSource; } // namespace ================================================ FILE: foo_spotify/fb2k/advanced_config.cpp ================================================ #include #include "advanced_config.h" namespace { advconfig_branch_factory branch_sptf( "Spotify Integration", sptf::guid::adv_branch, advconfig_branch::guid_branch_tools, 0 ); advconfig_branch_factory branch_network( "Network: restart is required", sptf::guid::adv_branch_network, sptf::guid::adv_branch, 0 ); advconfig_branch_factory branch_logging( "Logging: restart is required", sptf::guid::adv_branch_logging, sptf::guid::adv_branch, 1 ); } // namespace namespace sptf::config::advanced { qwr::fb2k::AdvConfigString_MT network_proxy( "Proxy: url", sptf::guid::adv_var_network_proxy, sptf::guid::adv_branch_network, 0, "" ); qwr::fb2k::AdvConfigString_MT network_proxy_username( "Proxy: username", sptf::guid::adv_var_network_proxy_username, sptf::guid::adv_branch_network, 1, "" ); qwr::fb2k::AdvConfigString_MT network_proxy_password( "Proxy: password", sptf::guid::adv_var_network_proxy_password, sptf::guid::adv_branch_network, 2, "" ); qwr::fb2k::AdvConfigBool_MT logging_webapi_request( "Log Spotify Web API: request", sptf::guid::adv_var_logging_webapi_request, sptf::guid::adv_branch_logging, 0, false ); qwr::fb2k::AdvConfigBool_MT logging_webapi_response( "Log Spotify Web API: response", sptf::guid::adv_var_logging_webapi_response, sptf::guid::adv_branch_logging, 1, false ); qwr::fb2k::AdvConfigBool_MT logging_webapi_debug( "Log Spotify Web API: debug", sptf::guid::adv_var_logging_webapi_debug, sptf::guid::adv_branch_logging, 2, false ); } // namespace sptf::config::advanced ================================================ FILE: foo_spotify/fb2k/advanced_config.h ================================================ #pragma once #include namespace sptf::config::advanced { extern qwr::fb2k::AdvConfigString_MT network_proxy; extern qwr::fb2k::AdvConfigString_MT network_proxy_username; extern qwr::fb2k::AdvConfigString_MT network_proxy_password; extern qwr::fb2k::AdvConfigBool_MT logging_webapi_request; extern qwr::fb2k::AdvConfigBool_MT logging_webapi_response; extern qwr::fb2k::AdvConfigBool_MT logging_webapi_debug; } // namespace sptf::config::advanced ================================================ FILE: foo_spotify/fb2k/album_art.cpp ================================================ #include #include #include #include #include using namespace sptf; namespace { class AlbumArtExtractorInstanceSpotify : public album_art_extractor_instance { public: AlbumArtExtractorInstanceSpotify( std::unique_ptr track, std::unique_ptr artist ); album_art_data_ptr query( const GUID& p_what, abort_callback& p_abort ) override; private: WebApi_Backend& waBackend_; std::unique_ptr track_; std::unique_ptr artist_; }; class AlbumArtExtractorSpotify : public album_art_extractor { public: AlbumArtExtractorSpotify() = default; bool is_our_path( const char* p_path, const char* p_extension ) override; album_art_extractor_instance_ptr open( file_ptr p_filehint, const char* p_path, abort_callback& p_abort ) override; }; } // namespace namespace { AlbumArtExtractorInstanceSpotify::AlbumArtExtractorInstanceSpotify( std::unique_ptr track, std::unique_ptr artist ) : waBackend_( SpotifyInstance::Get().GetWebApi_Backend() ) , track_( std::move( track ) ) , artist_( std::move( artist ) ) { assert( track_ ); assert( artist_ ); } album_art_data_ptr AlbumArtExtractorInstanceSpotify::query( const GUID& p_what, abort_callback& p_abort ) { const auto imagePath = [&] { if ( p_what == album_art_ids::cover_front ) { if ( track_->album->images.empty() ) { throw exception_album_art_not_found(); } return waBackend_.GetAlbumImage( track_->album->id, track_->album->images[0]->url, p_abort ); } else if ( p_what == album_art_ids::artist ) { if ( artist_->images.empty() ) { throw exception_album_art_not_found(); } return waBackend_.GetArtistImage( artist_->id, artist_->images[0]->url, p_abort ); } else { throw exception_album_art_not_found(); } }(); pfc::string8_fast canPath; filesystem::g_get_canonical_path( imagePath.u8string().c_str(), canPath ); file::ptr file; filesystem::g_open( file, canPath, filesystem::open_mode_read, p_abort ); return album_art_data_impl::g_create( file.get_ptr(), (size_t)file->get_size_ex( p_abort ), p_abort ); } bool AlbumArtExtractorSpotify::is_our_path( const char* p_path, const char* p_extension ) { return SpotifyFilteredTrack::IsValid( p_path, false ); } album_art_extractor_instance_ptr AlbumArtExtractorSpotify::open( file_ptr p_filehint, const char* p_path, abort_callback& p_abort ) { const auto spotifyObject = SpotifyFilteredTrack::Parse( p_path ); auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend(); auto track = waBackend.GetTrack( spotifyObject.Id(), p_abort ); auto artist = waBackend.GetArtist( track->artists[0]->id, p_abort ); if ( track->album->images.empty() && artist->images.empty() ) { throw exception_album_art_not_found(); } return ::fb2k::service_new( std::move( track ), std::move( artist ) ); } } // namespace namespace { static service_factory_single_t g_album_art_extractor; } ================================================ FILE: foo_spotify/fb2k/config.cpp ================================================ #include #include "config.h" namespace sptf::config { qwr::fb2k::ConfigBool_MT enable_normalization( sptf::guid::config_enable_normalization, true ); qwr::fb2k::ConfigBool_MT enable_private_mode( sptf::guid::config_enable_private_mode, false ); qwr::fb2k::ConfigUint8_MT libspotify_cache_size_in_percent( sptf::guid::config_libspotify_cache_size_in_percent, 10 ); qwr::fb2k::ConfigUint32_MT libspotify_cache_size_in_mb( sptf::guid::config_libspotify_cache_size_in_mb, 2048 ); qwr::fb2k::ConfigUint8Enum_MT preferred_bitrate( sptf::guid::config_preferred_bitrate, BitrateSettings::Bitrate320k ); qwr::fb2k::ConfigString_MT webapi_auth_scopes( sptf::guid::config_webapi_auth_scopes, "playlist-read-collaborative playlist-read-private user-read-private" ); } // namespace sptf::config ================================================ FILE: foo_spotify/fb2k/config.h ================================================ #pragma once #include namespace sptf::config { enum class BitrateSettings : uint8_t { // values are the same as in libspotify's sp_bitrate Bitrate160k = 0, Bitrate320k = 1, Bitrate96k = 2 }; extern qwr::fb2k::ConfigBool_MT enable_normalization; extern qwr::fb2k::ConfigBool_MT enable_private_mode; extern qwr::fb2k::ConfigUint8_MT libspotify_cache_size_in_percent; extern qwr::fb2k::ConfigUint32_MT libspotify_cache_size_in_mb; extern qwr::fb2k::ConfigUint8Enum_MT preferred_bitrate; extern qwr::fb2k::ConfigString_MT webapi_auth_scopes; } // namespace sptf::config ================================================ FILE: foo_spotify/fb2k/file_info_filler.cpp ================================================ #include #include "file_info_filler.h" #include namespace { void FillMetaInfo( const std::unordered_multimap& meta, file_info& info ) { auto addMeta = [&]( file_info& info, std::string_view metaName ) { const auto er = meta.equal_range( std::string( metaName.begin(), metaName.end() ) ); if ( er.first != er.second ) { const auto [key, val] = *er.first; info.meta_add( metaName.data(), val.c_str() ); } }; auto addMetaIfPositive = [&]( file_info& info, std::string_view metaName ) { const auto er = meta.equal_range( std::string( metaName.begin(), metaName.end() ) ); if ( er.first != er.second ) { const auto [key, val] = *er.first; auto numOpt = qwr::string::GetNumber( static_cast( val ) ); if ( numOpt && *numOpt ) { info.meta_add( metaName.data(), val.c_str() ); } } }; auto addMetaMultiple = [&]( file_info& info, std::string_view metaName ) { const auto er = meta.equal_range( std::string( metaName.begin(), metaName.end() ) ); for ( auto it = er.first; it != er.second; ++it ) { const auto [key, val] = *it; info.meta_add( metaName.data(), val.c_str() ); } }; addMeta( info, "TITLE" ); addMeta( info, "ALBUM" ); addMeta( info, "DATE" ); addMetaMultiple( info, "ARTIST" ); addMetaMultiple( info, "ALBUM ARTIST" ); addMetaIfPositive( info, "TRACKNUMBER" ); addMetaIfPositive( info, "DISCNUMBER" ); const auto er = meta.equal_range( "SPTF_LENGTH" ); if ( er.first != er.second ) { const auto [key, val] = *er.first; auto numOpt = qwr::string::GetNumber( static_cast( val ) ); if ( numOpt && *numOpt ) { info.set_length( *numOpt / 1000.0 ); } } } void FillTechnicalInfo( const std::unordered_multimap& meta, file_info& info ) { if ( pfc_infinite == info.info_find( "codec" ) ) { info.info_set( "codec", "Vorbis" ); } } } // namespace namespace sptf::fb2k { void FillFileInfoWithMeta( const std::unordered_multimap& meta, file_info& info ) { FillMetaInfo( meta, info ); FillTechnicalInfo( meta, info ); } } // namespace sptf::fb2k ================================================ FILE: foo_spotify/fb2k/file_info_filler.h ================================================ #pragma once #include #include namespace sptf::fb2k { void FillFileInfoWithMeta( const std::unordered_multimap& meta, file_info& info ); } ================================================ FILE: foo_spotify/fb2k/filesystem.cpp ================================================ #include #include using namespace std::literals::string_view_literals; using namespace sptf; namespace { class FilesystemSpotify : public foobar2000_io::filesystem { public: bool get_canonical_path( const char* p_path, pfc::string_base& p_out ) override; bool is_our_path( const char* p_path ) override; bool get_display_path( const char* p_path, pfc::string_base& p_out ) override; void open( service_ptr_t& p_out, const char* p_path, t_open_mode p_mode, abort_callback& p_abort ) override; void remove( const char* p_path, abort_callback& p_abort ) override; //! Moves/renames a file. Will fail if the destination file already exists. \n //! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios. void move( const char* p_src, const char* p_dst, abort_callback& p_abort ) override; //! Queries whether a file at specified path belonging to this filesystem is a remote object or not. bool is_remote( const char* p_src ) override; //! Retrieves stats of a file at specified path. void get_stats( const char* p_path, t_filestats& p_stats, bool& p_is_writeable, abort_callback& p_abort ) override; //! Creates a directory. void create_directory( const char* p_path, abort_callback& p_abort ) override; void list_directory( const char* p_path, directory_callback& p_out, abort_callback& p_abort ) override; //! Hint; returns whether this filesystem supports mime types. \n //! When this returns false, all file::get_content_type() calls on files opened thru this filesystem implementation will return false; otherwise, file::get_content_type() calls may return true depending on the file. bool supports_content_types() override; ; }; } // namespace namespace { bool FilesystemSpotify::get_canonical_path( const char* p_path, pfc::string_base& p_out ) { if ( !SpotifyFilteredTrack::IsValid( p_path, true ) ) { return false; } try { const auto ret = SpotifyObject( p_path ).ToSchema(); p_out = ret.c_str(); return true; } catch ( qwr::QwrException& ) { return false; } } bool FilesystemSpotify::is_our_path( const char* p_path ) { // accept only filtered input return SpotifyFilteredTrack::IsValid( p_path, true ); } bool FilesystemSpotify::get_display_path( const char* p_path, pfc::string_base& p_out ) { if ( !SpotifyFilteredTrack::IsValid( p_path, true ) ) { return false; } try { const auto ret = SpotifyObject( p_path ).ToUri(); p_out = ret.c_str(); return true; } catch ( qwr::QwrException& ) { return false; } } void FilesystemSpotify::open( service_ptr_t& p_out, const char* p_path, t_open_mode p_mode, abort_callback& p_abort ) { (void)p_abort; if ( p_mode != open_mode_read ) { throw pfc::exception( "Can't modify remote files" ); } p_out = ::fb2k::service_new(); } void FilesystemSpotify::remove( const char* p_path, abort_callback& p_abort ) { (void)p_path; (void)p_abort; throw pfc::exception( "Can't delete remote files" ); } void FilesystemSpotify::move( const char* p_src, const char* p_dst, abort_callback& p_abort ) { (void)p_src; (void)p_dst; (void)p_abort; throw pfc::exception( "Can't move remote files" ); } bool FilesystemSpotify::is_remote( const char* p_src ) { (void)p_src; return false; } void FilesystemSpotify::get_stats( const char* p_path, t_filestats& p_stats, bool& p_is_writeable, abort_callback& p_abort ) { (void)p_abort; p_stats = filestats_invalid; p_is_writeable = false; } void FilesystemSpotify::create_directory( const char* p_path, abort_callback& p_abort ) { (void)p_path; (void)p_abort; throw pfc::exception( "Directories are not supported" ); } void FilesystemSpotify::list_directory( const char* p_path, directory_callback& p_out, abort_callback& p_abort ) { (void)p_path; (void)p_out; (void)p_abort; throw pfc::exception( "Directories are not supported" ); } bool FilesystemSpotify::supports_content_types() { return false; } } // namespace namespace { static service_factory_single_t g_filesystem; } ================================================ FILE: foo_spotify/fb2k/input.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::literals::string_view_literals; using namespace sptf; namespace { // input_impl::input_impl class InputSpotify : public input_stubs , public LibSpotify_BackendUser { public: InputSpotify() = default; ~InputSpotify(); // SpotifyBackendUser void Finalize() override; // input_impl::open void open( service_ptr_t m_file, const char* p_path, t_input_open_reason p_reason, abort_callback& p_abort ); // input_info_reader::get_info void get_info( t_int32 subsong, file_info& p_info, abort_callback& p_abort ); // input_info_reader::get_file_stats t_filestats get_file_stats( abort_callback& p_abort ); // input_decoder::initialize void decode_initialize( t_int32 subsong, unsigned p_flags, abort_callback& p_abort ); // input_decoder::run bool decode_run( audio_chunk& p_chunk, abort_callback& p_abort ); // input_decoder::seek void decode_seek( double p_seconds, abort_callback& p_abort ); // input_decoder::can_seek bool decode_can_seek(); // input_decoder::get_dynamic_info bool decode_get_dynamic_info( file_info& p_out, double& p_timestamp_delta ); // input_decoder::get_dynamic_info_track bool decode_get_dynamic_info_track( file_info& p_out, double& p_timestamp_delta ); // input_decoder::on_idle void decode_on_idle( abort_callback& p_abort ); // input_impl::retag_set_info void retag_set_info( t_int32 subsong, const file_info& p_info, abort_callback& p_abort ); // input_impl::retag_commit void retag_commit( abort_callback& p_abort ); // input_info_reader::get_subsong_count t_uint32 get_subsong_count(); // input_info_reader::get_subsong t_uint32 get_subsong( unsigned song ); // input_entry::is_our_content_type static bool g_is_our_content_type( const char* p_content_type ); // input_entry::is_our_path static bool g_is_our_path( const char* p_full_path, const char* p_extension ); // input_entry::get_guid static GUID g_get_guid(); // input_entry::get_name static const char* g_get_name(); private: LibSpotify_Backend& GetInitializedLibSpotify(); private: bool usingLibSpotify_ = false; bool hasDecoder_ = false; std::optional openedReason_; wrapper::Ptr track_; std::string trackId_; std::unordered_multimap trackMeta_; bool isFirstBlock_ = false; int channels_{}; int sampleRate_{}; int bitRate_{}; }; } // namespace namespace { std::string GetPlaybackErrorMessage( sp_error sp, const std::string& trackId, abort_callback& p_abort ) { if ( sp == SP_ERROR_TRACK_NOT_PLAYABLE ) { try { auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend(); const auto track = waBackend.GetTrack( trackId, p_abort, true ); if ( track->restrictions ) { const auto& reason = track->restrictions.value()->reason; if ( reason == "market"sv ) { return "track is not accessible in your country"; } else if ( reason == "product"sv ) { return "track is not accessible for your subscription type"; } else if ( reason == "explicit"sv ) { return "track is explicit and your account is set to not play explicit content"; } else { return reason; } } } catch ( const std::exception& ) { } } return sp_error_message( sp ); } } // namespace namespace { InputSpotify::~InputSpotify() { try { Finalize(); } catch ( const std::exception& ) { } } void InputSpotify::Finalize() { if ( !usingLibSpotify_ ) { return; } usingLibSpotify_ = false; auto& lsBackend = SpotifyInstance::Get().GetLibSpotify_Backend(); { if ( hasDecoder_ ) { lsBackend.ReleaseDecoder( this ); } } if ( track_ ) { lsBackend.ExecSpMutex( [&] { track_.Release(); } ); } lsBackend.UnregisterBackendUser( *this ); } void InputSpotify::open( service_ptr_t m_file, const char* p_path, t_input_open_reason p_reason, abort_callback& p_abort ) { openedReason_ = p_reason; if ( p_reason == input_open_info_write ) { throw exception_io_denied_readonly(); } auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend(); const auto spotifyObject = SpotifyFilteredTrack::Parse( p_path ); trackId_ = spotifyObject.Id(); const auto track = waBackend.GetTrack( trackId_, p_abort ); trackMeta_ = waBackend.GetMetaForTracks( nonstd::span>( &track, 1 ) )[0]; if ( p_reason == input_open_info_read ) { // don't use LibSpotify stuff if it's not needed return; } auto& lsBackend = GetInitializedLibSpotify(); auto pSession = lsBackend.GetInitializedSpSession( p_abort ); lsBackend.ExecSpMutex( [&] { wrapper::Ptr link( sp_link_create_from_string( spotifyObject.ToUri().c_str() ) ); if ( !link ) { throw exception_io_data( "Couldn't parse url" ); } track_.Release(); switch ( sp_link_type( link ) ) { case SP_LINKTYPE_TRACK: { track_ = sp_link_as_track( link ); break; } default: { throw exception_io_data( "Only track links should be passed to input" ); } } } ); while ( true ) { // TODO: clean up here const auto done = lsBackend.ExecSpMutex( [&] { const auto sp = sp_track_error( track_ ); if ( SP_ERROR_OK == sp ) { return true; } else if ( SP_ERROR_IS_LOADING == sp ) { return false; } else { throw qwr::QwrException( fmt::format( "sp_track_error failed: {}", sp_error_message( sp ) ) ); } } ); if ( done ) { break; } p_abort.sleep( 0.05 ); } bitRate_ = [] { switch ( config::preferred_bitrate ) { case config::BitrateSettings::Bitrate96k: { return 96; } case config::BitrateSettings::Bitrate160k: { return 160; } case config::BitrateSettings::Bitrate320k: { return 320; } default: { assert( false ); return 320; } } }(); } void InputSpotify::get_info( t_int32 subsong, file_info& p_info, abort_callback& p_abort ) { if ( subsong ) { throw std::exception( "This track does not have any sub-songs" ); } sptf::fb2k::FillFileInfoWithMeta( trackMeta_, p_info ); if ( openedReason_ == input_open_decode ) { // Use exact length when possible auto& lsBackend = GetInitializedLibSpotify(); lsBackend.ExecSpMutex( [&] { p_info.set_length( sp_track_duration( track_ ) / 1000.0 ); } ); } } foobar2000_io::t_filestats InputSpotify::get_file_stats( abort_callback& p_abort ) { return t_filestats{}; } void InputSpotify::decode_initialize( t_int32 subsong, unsigned p_flags, abort_callback& p_abort ) { isFirstBlock_ = true; if ( subsong ) { throw std::exception( "This track does not have any sub-songs" ); } if ( !( p_flags & input_flag_playback ) ) { throw exception_io_denied(); } auto& lsBackend = GetInitializedLibSpotify(); lsBackend.AcquireDecoder( this ); hasDecoder_ = true; lsBackend.GetAudioBuffer().clear(); auto pSession = lsBackend.GetInitializedSpSession( p_abort ); lsBackend.ExecSpMutex( [&] { const auto sp = sp_session_player_load( pSession, track_ ); if ( sp != SP_ERROR_OK ) { throw qwr::QwrException( fmt::format( "sp_session_player_load failed: {}", GetPlaybackErrorMessage( sp, trackId_, p_abort ) ) ); } sp_session_player_play( pSession, true ); } ); } bool InputSpotify::decode_run( audio_chunk& p_chunk, abort_callback& p_abort ) { bool isEof = false; const auto dataReader = [&]( const AudioBuffer::AudioChunkHeader& header, const uint16_t* data ) { if ( header.eof ) { isEof = true; return; } channels_ = header.channels; sampleRate_ = header.sampleRate; p_chunk.set_data_fixedpoint( data, header.size * sizeof( uint16_t ), header.sampleRate, header.channels, 16, audio_chunk::channel_config_stereo ); }; auto& lsBackend = GetInitializedLibSpotify(); auto& buf = lsBackend.GetAudioBuffer(); if ( !buf.read( dataReader ) ) { buf.wait_for_data( p_abort ); if ( !buf.read( dataReader ) ) { // wait was aborted, ending playback isEof = true; } } if ( isEof ) { lsBackend.ReleaseDecoder( this ); hasDecoder_ = false; return false; } return true; } void InputSpotify::decode_seek( double p_seconds, abort_callback& p_abort ) { isFirstBlock_ = true; auto& lsBackend = GetInitializedLibSpotify(); lsBackend.GetAudioBuffer().clear(); auto pSession = lsBackend.GetInitializedSpSession( p_abort ); lsBackend.ExecSpMutex( [&] { sp_session_player_seek( pSession, static_cast( p_seconds * 1000 ) ); } ); } bool InputSpotify::decode_can_seek() { return true; } bool InputSpotify::decode_get_dynamic_info( file_info& p_out, double& p_timestamp_delta ) { if ( !isFirstBlock_ ) { return false; } p_out.info_set_int( "CHANNELS", channels_ ); p_out.info_set_int( "SAMPLERATE", sampleRate_ ); p_out.info_set_bitrate( bitRate_ ); isFirstBlock_ = false; return true; } bool InputSpotify::decode_get_dynamic_info_track( file_info& p_out, double& p_timestamp_delta ) { return false; } void InputSpotify::decode_on_idle( abort_callback& p_abort ) { (void)p_abort; } void InputSpotify::retag_set_info( t_int32 subsong, const file_info& p_info, abort_callback& p_abort ) { throw exception_io_data(); } void InputSpotify::retag_commit( abort_callback& p_abort ) { throw exception_io_data(); } bool InputSpotify::g_is_our_content_type( const char* p_content_type ) { return false; } bool InputSpotify::g_is_our_path( const char* p_full_path, const char* p_extension ) { (void)p_extension; // accept only filtered input return SpotifyFilteredTrack::IsValid( p_full_path, false ); } t_uint32 InputSpotify::get_subsong_count() { return 1; } t_uint32 InputSpotify::get_subsong( unsigned song ) { return song; } GUID InputSpotify::g_get_guid() { return guid::input; } const char* InputSpotify::g_get_name() { return SPTF_NAME ": decoder"; } LibSpotify_Backend& InputSpotify::GetInitializedLibSpotify() { auto& lsBackend = SpotifyInstance::Get().GetLibSpotify_Backend(); if ( !usingLibSpotify_ ) { lsBackend.RegisterBackendUser( *this ); usingLibSpotify_ = true; } return lsBackend; } } // namespace namespace { input_factory_t g_input; } // namespace ================================================ FILE: foo_spotify/fb2k/playback.cpp ================================================ #include #include "playback.h" #include namespace sptf::fb2k { std::mutex PlayCallbacks::mutex_; LibSpotify_Backend* PlayCallbacks::pLsBackend_ = nullptr; PlayCallbacks::PlayCallbacks() { } PlayCallbacks::~PlayCallbacks() { } void PlayCallbacks::Initialize( LibSpotify_Backend& lsBackend ) { std::lock_guard lg( mutex_ ); pLsBackend_ = &lsBackend; } void PlayCallbacks::Finalize() { std::lock_guard lg( mutex_ ); pLsBackend_ = nullptr; } unsigned PlayCallbacks::get_flags() { return ( flag_on_playback_stop | flag_on_playback_pause ); } void PlayCallbacks::on_playback_pause( bool isPaused ) { std::lock_guard lg( mutex_ ); if ( !pLsBackend_ ) { return; } auto pSession = pLsBackend_->GetWhateverSpSession(); pLsBackend_->ExecSpMutex( [&] { sp_session_player_play( pSession, !isPaused ); } ); } void PlayCallbacks::on_playback_stop( play_control::t_stop_reason reason ) { if ( play_control::stop_reason_starting_another == reason ) { return; } std::lock_guard lg( mutex_ ); if ( !pLsBackend_ ) { return; } auto pSession = pLsBackend_->GetWhateverSpSession(); pLsBackend_->ExecSpMutex( [&] { sp_session_player_unload( pSession ); } ); } } // namespace sptf::fb2k namespace { play_callback_static_factory_t g_play_callback_static; } ================================================ FILE: foo_spotify/fb2k/playback.h ================================================ #pragma once #include namespace sptf { class LibSpotify_Backend; } namespace sptf::fb2k { class PlayCallbacks : public play_callback_static { public: PlayCallbacks(); ~PlayCallbacks(); static void Initialize( LibSpotify_Backend& lsBackend ); static void Finalize(); // play_callback_static unsigned get_flags() override; void on_playback_pause( bool isPaused ) override; void on_playback_stop( play_control::t_stop_reason reason ) override; void on_playback_starting( play_control::t_track_command p_command, bool p_paused ) override{}; void on_playback_new_track( metadb_handle_ptr p_track ) override{}; void on_playback_seek( double p_time ) override{}; void on_playback_edited( metadb_handle_ptr p_track ) override{}; void on_playback_dynamic_info( const file_info& p_info ) override{}; void on_playback_dynamic_info_track( const file_info& p_info ) override{}; void on_playback_time( double p_time ) override{}; void on_volume_change( float p_new_val ) override{}; private: static std::mutex mutex_; static LibSpotify_Backend* pLsBackend_; }; } // namespace sptf::fb2k ================================================ FILE: foo_spotify/fb2k/playlist.cpp ================================================ #include #include #include #include #include #include #include using namespace sptf; namespace { class PlaylistCallbackSpotify : public playlist_callback_static { public: unsigned get_flags() override; void on_default_format_changed() override { } void on_item_ensure_visible( t_size p_playlist, t_size p_idx ) override { } void on_item_focus_change( t_size p_playlist, t_size p_from, t_size p_to ) override { } void on_items_added( t_size p_playlist, t_size p_start, metadb_handle_list_cref p_data, const pfc::bit_array& p_selection ) override { } void on_items_removing( t_size p_playlist, const pfc::bit_array& p_mask, t_size p_old_count, t_size p_new_count ) override { } void on_items_removed( t_size p_playlist, const pfc::bit_array& p_mask, t_size p_old_count, t_size p_new_count ) override { } void on_items_reordered( t_size p_playlist, const t_size* p_order, t_size p_count ) override { } void on_items_replaced( t_size p_playlist, const pfc::bit_array& p_mask, const pfc::list_base_const_t& p_data ) override { } void on_items_selection_change( t_size p_playlist, const pfc::bit_array& p_affected, const pfc::bit_array& p_state ) override { } void on_items_modified( t_size p_playlist, const pfc::bit_array& p_mask ) override { } void on_items_modified_fromplayback( t_size p_playlist, const pfc::bit_array& p_mask, play_control::t_display_level p_level ) override { } void on_playback_order_changed( t_size p_new_index ) override { } void on_playlist_activate( t_size p_old, t_size p_new ) override; void on_playlist_created( t_size p_index, const char* p_name, t_size p_name_len ) override { } void on_playlist_locked( t_size p_playlist, bool p_locked ) override { } void on_playlist_renamed( t_size p_index, const char* p_new_name, t_size p_new_name_len ) override { } void on_playlists_removing( const pfc::bit_array& p_mask, t_size p_old_count, t_size p_new_count ) override { } void on_playlists_removed( const pfc::bit_array& p_mask, t_size p_old_count, t_size p_new_count ) override { } void on_playlists_reorder( const t_size* p_order, t_size p_count ) override { } }; } // namespace namespace { unsigned PlaylistCallbackSpotify::get_flags() { return flag_on_playlist_activate; } void PlaylistCallbackSpotify::on_playlist_activate( t_size p_old, t_size p_new ) { if ( p_old == p_new ) { return; } metadb_handle_list items; playlist_manager::get()->playlist_get_all_items( p_new, items ); std::vector trackIds; for ( const auto& pMeta: qwr::pfc_x::Make_Stl_CRef( items ) ) { const char* path = pMeta->get_location().get_path(); if ( !SpotifyFilteredTrack::IsValid( path, false ) ) { continue; } trackIds.emplace_back( SpotifyFilteredTrack::Parse( path ).Id() ); } if ( trackIds.empty() ) { return; } auto& threadPool = SpotifyInstance::Get().GetThreadPool(); threadPool.AddTask( [trackIds = std::move( trackIds )] { try { auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend(); // pre-cache tracks qwr::TimedAbortCallback tac1; const auto tracks = waBackend.GetTracks( trackIds, tac1 ); const auto artistIds = tracks | ranges::views::transform( []( const auto& pTrack ) -> std::string { return pTrack->artists[0]->id; } ) | ranges::to_vector; // pre-cache artists qwr::TimedAbortCallback tac2; waBackend.RefreshCacheForArtists( artistIds, tac2 ); } catch ( const std::exception& e ) { FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (error):\n" << "Failed to refresh tracks:\n" << e.what(); } } ); } } // namespace namespace { service_factory_single_t g_playlist_callback_static; } ================================================ FILE: foo_spotify/fb2k/playlist_loader.cpp ================================================ #include #include #include #include #include #include #include #include using namespace std::literals::string_view_literals; using namespace sptf; namespace { class PlaylistLoaderSpotify : public playlist_loader { public: PlaylistLoaderSpotify() = default; public: // playlist_loader //! Parses specified playlist file into list of playable locations. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. //! @param p_path Path of playlist file to parse. Used for relative path handling purposes (p_file parameter is used for actual file access). //! @param p_file File interface to use for reading. Read/write pointer must be set to beginning by caller before calling. //! @param p_callback Callback object receiving enumerated playable item locations. void open( const char* p_path, const service_ptr_t& p_file, playlist_loader_callback::ptr p_callback, abort_callback& p_abort ) override; //! Writes a playlist file containing specific item list to specified file. Will fail (pfc::exception_not_implemented) if specified playlist_loader is read-only (can_write() returns false). //! @param p_path Path of playlist file to write. Used for relative path handling purposes (p_file parameter is used for actual file access). //! @param p_file File interface to use for writing. Caller should ensure that the file is empty (0 bytes long) before calling. //! @param p_data List of items to save to playlist file. //! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file. void write( const char* p_path, const service_ptr_t& p_file, metadb_handle_list_cref p_data, abort_callback& p_abort ) override; //! Returns extension of file format handled by this playlist_loader implementation (a UTF-8 encoded null-terminated string). const char* get_extension() override; //! Returns whether this playlist_loader implementation supports writing. If can_write() returns false, all write() calls will fail. bool can_write() override; //! Returns whether specified content type is one of playlist types supported by this playlist_loader implementation or not. //! @param p_content_type Content type to query, a UTF-8 encoded null-terminated string. bool is_our_content_type( const char* p_content_type ) override; //! Returns whether playlist format extension supported by this implementation should be listed on file types associations page. bool is_associatable() override; /* //! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. \n //! Equivalent to g_load_playlist_filehint(NULL,p_path,p_callback). //! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string. //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. static void g_load_playlist( const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort ); //! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. //! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string. //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. //! @param fileHint File object to read from, can be NULL if not available. static void g_load_playlist_filehint( file::ptr fileHint, const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort ); //! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, returns false; returns true upon successful playlist load. //! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string. //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. //! @param fileHint File object to read from, can be NULL if not available. static bool g_try_load_playlist( file::ptr fileHint, const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort ); //! Saves specified list of locations into a playlist file. Throws exception_io or derivatives on failure, exception_aborted on abort. //! @param p_path Filesystem path to save playlist to, a UTF-8 encoded null-terminated string. //! @param p_data List of items to save to playlist file. //! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file. static void g_save_playlist( const char* p_path, metadb_handle_list_cref p_data, abort_callback& p_abort ); //! Processes specified path to generate list of playable items. Includes recursive directory/archive enumeration. \n //! Does not touch playlist files encountered - use g_process_path_ex() if specified path is possibly a playlist file; playlist files found inside directories or archives are ignored regardless.\n //! Warning: caller must handle exceptions which will occur in case of I/O failure. //! @param p_path Filesystem path to process; a UTF-8 encoded null-terminated string. //! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation. //! @param p_type Origin of p_path string. Reserved for internal use in recursive calls, should be left at default value; it controls various internal behaviors. static void g_process_path( const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort, playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested ); //! Calls attempts to process specified path as a playlist; if that fails (i.e. not a playlist), calls g_process_path with same parameters. See g_process_path for parameter descriptions. \n //! Warning: caller must handle exceptions which will occur in case of I/O failure or playlist parsing failure. //! @returns True if specified path was processed as a playlist file, false otherwise (relevant in some scenarios where output is sorted after loading, playlist file contents should not be sorted). static bool g_process_path_ex( const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort, playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested ); */ }; } // namespace namespace { struct SkippedTrack { std::string name; std::string reason; }; std::vector TransformToSkippedTracks( nonstd::span> tracks ) { return ranges::views::transform( tracks, [&]( const auto& pTrack ) -> SkippedTrack { return { ( pTrack->name ? *pTrack->name : pTrack->uri ), "local track" }; } ) | ranges::to_vector; } std::tuple>, std::vector> GetTracks( const SpotifyObject spotifyObject, abort_callback& p_abort ) { auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend(); const auto preCacheArtists = [&waBackend]( const auto& tracks, auto& p_abort ) { const auto artistIds = tracks | ranges::views::transform( []( const auto& pTrack ) -> std::string { return pTrack->artists[0]->id; } ) | ranges::to_vector; waBackend.RefreshCacheForArtists( artistIds, p_abort ); }; if ( spotifyObject.type == "album" ) { auto tracks = waBackend.GetTracksFromAlbum( spotifyObject.id, p_abort ); preCacheArtists( tracks, p_abort ); return { std::move( tracks ), std::vector{} }; } else if ( spotifyObject.type == "playlist" ) { auto [tracks, localTracks] = waBackend.GetTracksFromPlaylist( spotifyObject.id, p_abort ); preCacheArtists( tracks, p_abort ); // ??? Dunno why this is required. Smth to do with structured bindings and RVO. return { std::move( tracks ), TransformToSkippedTracks( localTracks ) }; } else if ( spotifyObject.type == "artist" ) { return { waBackend.GetTopTracksForArtist( spotifyObject.id, p_abort ), std::vector{} }; } else if ( spotifyObject.type == "track" ) { std::vector> tmp; tmp.emplace_back( waBackend.GetTrack( spotifyObject.id, p_abort ) ); return { std::move( tmp ), std::vector{} }; } else if ( spotifyObject.type == "local" ) { std::vector tmp; tmp.emplace_back( SkippedTrack{ spotifyObject.id, "local track" } ); return { std::vector>{}, tmp }; } else { throw qwr::QwrException( "Unexpected Spotify object type: {}", spotifyObject.type ); } } void ReportSkippedTracks( nonstd::span skippedTracks ) { if ( skippedTracks.empty() ) { return; } const auto formatedSkippedTracks = ranges::views::transform( skippedTracks, [&]( const auto& track ) -> std::string { return fmt::format( "{}: {}", track.name, track.reason ); } ) | ranges::to_vector; const auto msg = fmt::format( "Failed to add the following tracks:\n" " {}\n", fmt::join( formatedSkippedTracks, "\n " ) ); qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, msg ); } } // namespace namespace { void PlaylistLoaderSpotify::open( const char* p_path, const service_ptr_t& p_file, playlist_loader_callback::ptr p_callback, abort_callback& p_abort ) { const auto spotifyObject = [&] { try { return SpotifyObject( p_path ); } catch ( const qwr::QwrException& ) { throw exception_io_unsupported_format(); } }(); p_callback->on_progress( p_path ); auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend(); auto [tracks, skippedTracks] = GetTracks( spotifyObject, p_abort ); std::vector relinkedTracks; const auto tracksMeta = waBackend.GetMetaForTracks( tracks ); for ( const auto& [track, trackMeta]: ranges::views::zip( tracks, tracksMeta ) ) { file_info_impl f_info; sptf::fb2k::FillFileInfoWithMeta( trackMeta, f_info ); metadb_handle_ptr f_handle; p_callback->handle_create( f_handle, make_playable_location( SpotifyFilteredTrack( track->id ).ToSchema().c_str(), 0 ) ); p_callback->on_entry_info( f_handle, playlist_loader_callback::entry_user_requested, filestats_invalid, f_info, false ); } ReportSkippedTracks( skippedTracks ); } void PlaylistLoaderSpotify::write( const char* p_path, const service_ptr_t& p_file, metadb_handle_list_cref p_data, abort_callback& p_abort ) { throw pfc::exception_not_implemented(); } const char* PlaylistLoaderSpotify::get_extension() { return ""; } bool PlaylistLoaderSpotify::can_write() { return false; } bool PlaylistLoaderSpotify::is_our_content_type( const char* p_content_type ) { constexpr auto mime = "text/html; charset=utf-8"sv; const auto lowerContentType = [&p_content_type]() { // Content type should not contain anything other than ASCII return std::string_view{ p_content_type } | ranges::views::transform( []( auto i ) { return static_cast( ::tolower( i ) ); } ) | ranges::to; }(); return ( mime == lowerContentType ); } bool PlaylistLoaderSpotify::is_associatable() { return false; } } // namespace namespace { playlist_loader_factory_t g_playlist_loader; } // namespace ================================================ FILE: foo_spotify/foo_spotify.rc ================================================ // Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #define APSTUDIO_HIDDEN_SYMBOLS #include "windows.h" #undef APSTUDIO_HIDDEN_SYMBOLS #include #include ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_NOT_AUTH DIALOGEX 0, 0, 249, 78 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dialog" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "OK",IDOK,30,56,78,14 PUSHBUTTON "Authenticate...",IDC_BTN_OPEN_PREF,141,56,78,14 LTEXT "Static",IDC_STATIC_NOT_AUTH,7,7,235,41 END IDD_PREF_TAB_HOST DIALOGEX 0, 0, 334, 288 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_CLIPSIBLINGS EXSTYLE WS_EX_CONTROLPARENT FONT 8, "MS Shell Dlg", 400, 0, 0x0 BEGIN CONTROL "Tabs",IDC_TAB_HOST,"SysTabControl32",WS_TABSTOP,0,0,332,287 END IDD_PREF_TAB_AUTH DIALOGEX 0, 0, 327, 268 STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD FONT 8, "MS Shell Dlg", 400, 0, 0x0 BEGIN GROUPBOX "LibSpotify",IDC_STATIC,11,13,282,40 PUSHBUTTON "LS Login",IDC_BTN_LOGIN_LIBSPOTIFY,17,28,50,14 LTEXT "LS status:",IDC_STATIC_LIBSPOTIFY_STATUS,72,30,217,19 GROUPBOX "WebAPI",IDC_STATIC,11,61,282,40 PUSHBUTTON "WA Login",IDC_BTN_LOGIN_WEBAPI,17,76,50,14 LTEXT "WA status:",IDC_STATIC_WEBAPI_STATUS,72,78,217,19 END IDD_PREF_TAB_PLAYBACK DIALOGEX 0, 0, 327, 268 STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD FONT 8, "MS Shell Dlg", 400, 0, 0x0 BEGIN GROUPBOX "Playback",IDC_STATIC,11,13,285,87 LTEXT "Prefered bitrate",IDC_STATIC,22,28,52,8 COMBOBOX IDC_COMBO_BITRATE,22,39,126,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP CONTROL "Enable volume normalization",IDC_CHECK_NORMALIZE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,24,62,106,10 CONTROL "Enable private mode",IDC_CHECK_PRIVATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,24,77,81,10 GROUPBOX "Cache",IDC_STATIC,11,114,285,99 LTEXT "Maximum size in MB (0 - disabled)",IDC_STATIC,29,132,107,8 CONTROL "",IDC_SLIDER_CACHE_MB,"msctls_trackbar32",TBS_AUTOTICKS | TBS_TOOLTIPS | TBS_NOTIFYBEFOREMOVE | WS_TABSTOP,19,142,148,15 EDITTEXT IDC_EDIT_CACHE_MB,170,141,40,14,ES_AUTOHSCROLL | ES_NUMBER LTEXT "Maximum size in % of total free space (0 - disabled)",IDC_STATIC,29,169,122,8 CONTROL "",IDC_SLIDER_CACHE_PERCENT,"msctls_trackbar32",TBS_AUTOTICKS | TBS_TOOLTIPS | TBS_NOTIFYBEFOREMOVE | WS_TABSTOP,19,179,148,15 EDITTEXT IDC_EDIT_CACHE_PERCENT,170,178,40,14,ES_AUTOHSCROLL | ES_NUMBER END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO BEGIN IDD_NOT_AUTH, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 242 TOPMARGIN, 7 BOTTOMMARGIN, 70 END IDD_PREF_TAB_HOST, DIALOG BEGIN END IDD_PREF_TAB_AUTH, DIALOG BEGIN END IDD_PREF_TAB_PLAYBACK, DIALOG BEGIN END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // AFX_DIALOG_LAYOUT // IDD_PREF_TAB_HOST AFX_DIALOG_LAYOUT BEGIN 0 END IDD_NOT_AUTH AFX_DIALOG_LAYOUT BEGIN 0 END IDD_PREF_TAB_AUTH AFX_DIALOG_LAYOUT BEGIN 0 END IDD_PREF_TAB_PLAYBACK AFX_DIALOG_LAYOUT BEGIN 0 END #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" "#include ""windows.h""\r\n" "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" "\r\n" "#include \r\n" "#include \r\n" "\0" END 3 TEXTINCLUDE BEGIN "#include ""foo_spotify.rc2""\0" END #endif // APSTUDIO_INVOKED #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // #include "foo_spotify.rc2" ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: foo_spotify/foo_spotify.rc2 ================================================ // // foo_spotify.rc2 - resources Microsoft Visual C++ does not edit directly // #ifdef APSTUDIO_INVOKED #error this file is not editable by Microsoft Visual C++ #endif // APSTUDIO_INVOKED #include #if !defined( AFX_RESOURCE_DLL ) || defined( AFX_TARG_ENU ) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US # pragma code_page( 1252 ) ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION SPTF_VERSION_MAJOR,SPTF_VERSION_MINOR,SPTF_VERSION_PATCH,0 PRODUCTVERSION SPTF_VERSION_MAJOR,SPTF_VERSION_MINOR,SPTF_VERSION_PATCH,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG #ifdef SPTF_VERSION_PRERELEASE_TEXT FILEFLAGS 0x3L #else FILEFLAGS 0x1L #endif #else #ifdef SPTF_VERSION_PRERELEASE_TEXT FILEFLAGS 0x2L #else FILEFLAGS 0x0L #endif #endif FILEOS 0x40004L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "FileDescription", SPTF_NAME " component for foobar2000" VALUE "FileVersion", SPTF_VERSION VALUE "InternalName", SPTF_DLL_NAME VALUE "LegalCopyright", "Copyright (C) 2020 Yuri Shutenko" VALUE "OriginalFilename", SPTF_DLL_NAME VALUE "ProductName", SPTF_NAME VALUE "ProductVersion", SPTF_VERSION END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif ================================================ FILE: foo_spotify/foo_spotify.vcxproj ================================================  Debug Win32 Release Win32 foo_spotify 10.0.18362.0 {FB107A12-DEFC-4782-97FB-EC155B13550C} x86-windows-static DynamicLibrary Unicode v142 true true $(IncludePath);$(CAExcludePath) $(ProjectDir);$(IncludePath);%(AdditionalIncludeDirectories) stdcpp17 /Zm200 /Zc:__cplusplus /Zc:preprocessor /experimental:newLambdaProcessor %(AdditionalOptions) Use 5105 true true false true ProgramDatabase -await %(AdditionalOptions) credui.lib;%(AdditionalDependencies) Windows /SOURCELINK:$(IndependentGeneratedDir)source_link.json %(AdditionalOptions) _WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks MachineX86 false _WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS;%(PreprocessorDefinitions) true false Fast 0x0409 true true MachineX86 Create stdafx.h {ee3bf4f9-2014-4cff-96c8-44cfb85e0571} {71ad2674-065b-48f5-b8b0-e1f9d3892081} {e8091321-d79d-4575-86ef-064ea1a4a20d} {ebfffb4e-261d-44d3-b89c-957b31a0bf9c} This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. ================================================ FILE: foo_spotify/foo_spotify.vcxproj.filters ================================================  fb2k backend utils fb2k fb2k backend backend fb2k backend\webapi_objects backend\webapi_objects backend\webapi_objects backend\webapi_objects fb2k backend backend fb2k backend\webapi_objects utils backend\webapi_objects backend ui fb2k fb2k backend fb2k backend backend\webapi_objects backend\webapi_objects utils ui ui ui utils fb2k backend\webapi_objects utils backend backend backend utils backend backend backend utils backend backend backend\webapi_objects backend\webapi_objects backend\webapi_objects backend\webapi_objects utils utils fb2k backend fb2k utils resources backend\webapi_objects backend\webapi_objects backend fb2k ui fb2k backend backend\webapi_objects backend\webapi_objects backend\webapi_objects utils ui ui ui ui utils backend\webapi_objects {39367257-b161-4f23-b132-bcf2bc7a7b1e} {b367b073-47b1-4060-9fb1-165c7d6558c8} {a36e2bb9-5034-4b8e-9275-aac759906267} {9f4d57e0-bdd7-495d-a54d-0c753b8c738e} {20a66e7a-1826-4915-8807-e86a5c3df4f8} {4cc30a63-061e-4a1c-a2f1-8ecc63d6f357} resources resources ================================================ FILE: foo_spotify/packages.config ================================================  ================================================ FILE: foo_spotify/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by foo_spotify.rc // #define VS_VERSION_INFO 1 #define IDD_PREF_TAB_HOST 101 #define IDD_NOT_AUTH 102 #define IDD_PREF_TAB_AUTH 103 #define IDD_PREF_TAB_PLAYBACK 104 #define IDC_BTN_LOGIN_LIBSPOTIFY 1003 #define IDC_BTN_LOGIN_WEBAPI 1004 #define IDC_STATIC_LIBSPOTIFY_STATUS 1005 #define IDC_STATIC_WEBAPI_STATUS 1006 #define IDC_BTN_OPEN_PREF 1007 #define IDC_STATIC_NOT_AUTH 1008 #define IDC_COMBO_BITRATE 1009 #define IDC_CHECK_NORMALIZE 1010 #define IDC_CHECK_PRIVATE 1011 #define IDC_TAB_HOST 1012 #define IDC_SLIDER_CACHE_MB 1013 #define IDC_SLIDER_CACHE_PERCENT 1014 #define IDC_EDIT_CACHE_MB 1015 #define IDC_EDIT_CACHE_PERCENT 1016 // Next default values for new objects // #ifdef APSTUDIO_INVOKED # ifndef APSTUDIO_READONLY_SYMBOLS # define _APS_NEXT_RESOURCE_VALUE 105 # define _APS_NEXT_COMMAND_VALUE 40001 # define _APS_NEXT_CONTROL_VALUE 1017 # define _APS_NEXT_SYMED_VALUE 101 # endif #endif ================================================ FILE: foo_spotify/stdafx.cpp ================================================ #include ================================================ FILE: foo_spotify/stdafx.h ================================================ #pragma once // clang-format off // !!! Include order is important here (esp. for Win headers) !!! // Support only Windows 7 and newer #define _WIN32_WINNT _WIN32_WINNT_WIN7 #define WINVER _WIN32_WINNT_WIN7 // Fix std min max conflicts #define NOMINMAX #include #include // ATL/WTL /// atlstr.h (includes atlbase.h) must be included first for CString to LPTSTR conversion to work. #include #include #include #include #include #include #include #include #include #include #include #include // foobar2000 SDK #pragma warning( push, 0 ) # include #pragma warning( pop ) // fmt #define FMT_HEADER_ONLY #include // nlohmann json #include // range v3 #include #if not __cpp_char8_t // Dummy type #include using char8_t = char; namespace std // NOLINT(cert-dcl58-cpp) { using u8string = basic_string, allocator>; using u8string_view = basic_string_view; } #endif // Additional PFC wrappers #include #include #include #include #include #include #include #include // clang-format on ================================================ FILE: foo_spotify/ui/ui_ipref_tab.h ================================================ #pragma once namespace sptf::ui { class IPrefTab { public: virtual ~IPrefTab() = default; virtual HWND CreateTab( HWND hParent ) = 0; virtual CDialogImplBase& Dialog() = 0; virtual const wchar_t* Name() const = 0; // preferences_page_instance //! @returns a combination of preferences_state constants. virtual uint32_t get_state() = 0; //! @returns the window handle. //! Applies preferences changes. virtual void apply() = 0; //! Resets this page's content to the default values. Does not apply any changes - lets user preview the changes before hitting "apply". virtual void reset() = 0; }; } // namespace sptf::ui ================================================ FILE: foo_spotify/ui/ui_not_auth.cpp ================================================ #include #include "ui_not_auth.h" #include namespace sptf::ui { NotAuth::NotAuth( std::function onDestroyCallback ) : onDestroyCallback_( onDestroyCallback ) { } BOOL NotAuth::OnInitDialog( HWND hwndFocus, LPARAM lParam ) { hookId_ = qwr::HookHandler::GetInstance().RegisterHook( [hParent = m_hWnd]( int code, WPARAM wParam, LPARAM lParam ) { GetMsgProc( code, wParam, lParam, hParent ); } ); SetWindowText( TEXT( SPTF_NAME ) ); CStatic text( GetDlgItem( IDC_STATIC_NOT_AUTH ) ); text.SetWindowText( fmt::format( L"{} ({}) requires Spotify Premium account.\n" L"Open the corresponding foobar2000 Preferences page to authorize.", TEXT( SPTF_NAME ), TEXT( SPTF_UNDERSCORE_NAME ) ) .c_str() ); return TRUE; // set focus to default control } void NotAuth::OnDestroy() { if ( hookId_ ) { qwr::HookHandler::GetInstance().UnregisterHook( hookId_ ); hookId_ = 0; } } void NotAuth::OnCloseCmd( UINT uNotifyCode, int nID, CWindow wndCtl ) { DestroyWindow(); } void NotAuth::OnOpenPrefClick( UINT uNotifyCode, int nID, CWindow wndCtl ) { ::fb2k::inMainThread( [] { ui_control::get()->show_preferences( sptf::guid::preferences ); } ); DestroyWindow(); } void NotAuth::OnFinalMessage( _In_ HWND /*hWnd*/ ) { onDestroyCallback_(); delete this; } void NotAuth::GetMsgProc( int, WPARAM, LPARAM lParam, HWND hParent ) { if ( auto pMsg = reinterpret_cast( lParam ); pMsg->message >= WM_KEYFIRST && pMsg->message <= WM_KEYLAST ) { // Only react to keypress events HWND hWndFocus = ::GetFocus(); if ( hWndFocus != nullptr && ( ( hParent == hWndFocus ) || ::IsChild( hParent, hWndFocus ) ) ) { if ( ::IsDialogMessage( hParent, pMsg ) ) { pMsg->message = WM_NULL; } } } } NotAuthHandler& NotAuthHandler::Get() { static NotAuthHandler nah; return nah; } void NotAuthHandler::ShowDialog() { assert( core_api::is_main_thread() ); if ( !pNotAuth_ ) { pNotAuth_ = new ui::NotAuth( [&] { assert( core_api::is_main_thread() ); pNotAuth_ = nullptr; } ); if ( !pNotAuth_->Create( core_api::get_main_window() ) ) { delete pNotAuth_; pNotAuth_ = nullptr; return; } pNotAuth_->SetActiveWindow(); pNotAuth_->CenterWindow( core_api::get_main_window() ); pNotAuth_->ShowWindow( SW_SHOW ); } else { pNotAuth_->SetActiveWindow(); pNotAuth_->ShowWindow( SW_SHOW ); } } void NotAuthHandler::CloseDialog() { assert( core_api::is_main_thread() ); if ( pNotAuth_ ) { pNotAuth_->DestroyWindow(); } } } // namespace sptf::ui ================================================ FILE: foo_spotify/ui/ui_not_auth.h ================================================ #pragma once #include #include namespace sptf::ui { class NotAuth : public CDialogImpl { public: enum { IDD = IDD_NOT_AUTH }; NotAuth( std::function onDestroyCallback ); ~NotAuth() = default; protected: BEGIN_MSG_MAP( NotAuth ) MSG_WM_INITDIALOG( OnInitDialog ) MSG_WM_DESTROY( OnDestroy ) COMMAND_RANGE_HANDLER_EX( IDOK, IDCANCEL, OnCloseCmd ) COMMAND_HANDLER_EX( IDC_BTN_OPEN_PREF, BN_CLICKED, OnOpenPrefClick ) END_MSG_MAP() private: BOOL OnInitDialog( HWND hwndFocus, LPARAM lParam ); void OnDestroy(); void OnCloseCmd( UINT uNotifyCode, int nID, CWindow wndCtl ); void OnOpenPrefClick( UINT uNotifyCode, int nID, CWindow wndCtl ); void OnFinalMessage( _In_ HWND hWnd ) override; static void GetMsgProc( int code, WPARAM wParam, LPARAM lParam, HWND hParent ); private: uint32_t hookId_ = 0; std::function onDestroyCallback_; }; class NotAuthHandler { public: ~NotAuthHandler() = default; static NotAuthHandler& Get(); void ShowDialog(); void CloseDialog(); private: NotAuthHandler() = default; private: std::mutex authMutex_; sptf::ui::NotAuth* pNotAuth_ = nullptr; }; } // namespace sptf::ui ================================================ FILE: foo_spotify/ui/ui_pref_tab_auth.cpp ================================================ #include #include "ui_pref_tab_auth.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace sptf::ui { PreferenceTabAuth::PreferenceTabAuth( PreferenceTabManager* pParent ) : pParent_( pParent ) , ddxOptions_( {} ) { } PreferenceTabAuth::~PreferenceTabAuth() { for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Option().Revert(); } } HWND PreferenceTabAuth::CreateTab( HWND hParent ) { return Create( hParent ); } CDialogImplBase& PreferenceTabAuth::Dialog() { return *this; } const wchar_t* PreferenceTabAuth::Name() const { return L"Authorization"; } t_uint32 PreferenceTabAuth::get_state() { const bool hasChanged = ddxOptions_.cend() != std::find_if( ddxOptions_.cbegin(), ddxOptions_.cend(), []( const auto& ddxOpt ) { return ddxOpt->Option().HasChanged(); } ); return ( preferences_state::resettable | ( hasChanged ? preferences_state::changed : 0 ) ); } void PreferenceTabAuth::apply() { for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Option().Apply(); } pParent_->OnDataChanged(); } void PreferenceTabAuth::reset() { for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Option().ResetToDefault(); } UpdateUiFromCfg(); pParent_->OnDataChanged(); } BOOL PreferenceTabAuth::OnInitDialog( HWND hwndFocus, LPARAM lParam ) { btnLibSpotify_ = GetDlgItem( IDC_BTN_LOGIN_LIBSPOTIFY ); btnWebApi_ = GetDlgItem( IDC_BTN_LOGIN_WEBAPI ); textLibSpotify_ = GetDlgItem( IDC_STATIC_LIBSPOTIFY_STATUS ); textWebApi_ = GetDlgItem( IDC_STATIC_WEBAPI_STATUS ); for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Ddx().SetHwnd( m_hWnd ); } UpdateUiFromCfg(); pStatusThread_ = std::make_unique( [this] { const auto lsStatus = [] { qwr::TimedAbortCallback tac( fmt::format( "{}: {}", SPTF_UNDERSCORE_NAME, "LibSpotify relogin" ) ); return SpotifyInstance::Get().GetLibSpotify_Backend().Relogin( tac ); }(); const auto waStatus = [&] { auto& auth = SpotifyInstance::Get().GetWebApi_Backend().GetAuthorizer(); if ( !auth.HasRefreshToken() ) { return false; } try { qwr::TimedAbortCallback tac( fmt::format( "{}: {}", SPTF_UNDERSCORE_NAME, "WebApi relogin" ) ); auth.UpdateRefreshToken( tac ); return true; } catch ( const std::exception& ) { return false; } }(); ::PostMessage( m_hWnd, kOnStatusUpdateFinish, WPARAM( lsStatus ), LPARAM( waStatus ) ); } ); UpdateLibSpotifyUi(); UpdateWebApiUi(); return TRUE; // set focus to default control } void PreferenceTabAuth::OnDestroy() { auto& auth = SpotifyInstance::Get().GetWebApi_Backend().GetAuthorizer(); if ( webApiStatus_ == LoginStatus::logged_in ) { auth.AuthenticateClean_Cleanup(); } else { auth.CancelAuth(); } if ( pStatusThread_ ) { if ( pStatusThread_->joinable() ) { pStatusThread_->join(); } pStatusThread_.reset(); } } HBRUSH PreferenceTabAuth::OnCtlColorStatic( CDCHandle dc, CStatic wndStatic ) { const auto id = wndStatic.GetDlgCtrlID(); if ( id == IDC_STATIC_LIBSPOTIFY_STATUS || id == IDC_STATIC_WEBAPI_STATUS ) { const auto getColour = []( auto loginStatus ) { switch ( loginStatus ) { case LoginStatus::logged_out: { return RGB( 0x8B, 0x00, 0x00 ); } case LoginStatus::logged_in: { return RGB( 0x2F, 0x4F, 0x4f ); } default: return RGB( 0xFF, 0x7F, 0x50 ); } }; ::SetTextColor( dc, getColour( id == IDC_STATIC_LIBSPOTIFY_STATUS ? libSpotifyStatus_ : webApiStatus_ ) ); ::SetBkColor( dc, GetSysColor( COLOR_WINDOW ) ); return (HBRUSH)GetStockObject( HOLLOW_BRUSH ); } SetMsgHandled( FALSE ); return 0; } void PreferenceTabAuth::OnDdxChange( UINT uNotifyCode, int nID, CWindow wndCtl ) { auto it = std::find_if( ddxOptions_.begin(), ddxOptions_.end(), [nID]( auto& val ) { return val->Ddx().IsMatchingId( nID ); } ); if ( ddxOptions_.end() != it ) { ( *it )->Ddx().ReadFromUi(); pParent_->OnDataChanged(); } } void PreferenceTabAuth::OnLibSpotifyLoginClick( UINT uNotifyCode, int nID, CWindow wndCtl ) { (void)uNotifyCode; (void)nID; (void)wndCtl; auto& lsBackend = SpotifyInstance::Get().GetLibSpotify_Backend(); if ( libSpotifyStatus_ == LoginStatus::logged_out ) { libSpotifyStatus_ = ( lsBackend.LoginWithUI( m_hWnd ) ? LoginStatus::logged_in : LoginStatus::logged_out ); } else { qwr::TimedAbortCallback tac( fmt::format( "{}: {}", SPTF_UNDERSCORE_NAME, "LibSpotify logout" ) ); lsBackend.LogoutAndForget( tac ); libSpotifyStatus_ = LoginStatus::logged_out; } UpdateLibSpotifyUi(); } void PreferenceTabAuth::OnWebApiLoginClick( UINT uNotifyCode, int nID, CWindow wndCtl ) { (void)uNotifyCode; (void)nID; (void)wndCtl; auto& auth = SpotifyInstance::Get().GetWebApi_Backend().GetAuthorizer(); if ( webApiStatus_ == LoginStatus::logged_out ) { // this operation is async webApiStatus_ = LoginStatus::login_in_progress; UpdateWebApiUi(); try { const auto authScopes = qwr::unicode::ToWide( static_cast( config::webapi_auth_scopes ) ); const auto authScopesSplit = qwr::string::Split( authScopes, L' ' ); // TODO: remove me WebApiAuthScopes tmp( authScopesSplit ); tmp.user_read_private = true; auth.AuthenticateClean( tmp, [&] { ::PostMessage( m_hWnd, kOnWebApiLoginResponse, 0, 0 ); } ); } catch ( const std::exception& e ) { qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, fmt::format( "WebAPI login failed:\n{}", e.what() ) ); webApiStatus_ = LoginStatus::logged_out; UpdateWebApiUi(); } } else { auth.ClearAuth(); webApiStatus_ = LoginStatus::logged_out; UpdateWebApiUi(); } } LRESULT PreferenceTabAuth::OnWebApiLoginResponse( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { (void)uMsg; (void)wParam; (void)lParam; bHandled = TRUE; auto& auth = SpotifyInstance::Get().GetWebApi_Backend().GetAuthorizer(); webApiStatus_ = ( auth.HasRefreshToken() ? LoginStatus::logged_in : LoginStatus::logged_out ); UpdateWebApiUi(); return 0; } LRESULT PreferenceTabAuth::OnStatusUpdateFinish( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { (void)uMsg; bHandled = TRUE; libSpotifyStatus_ = ( wParam ? LoginStatus::logged_in : LoginStatus::logged_out ); webApiStatus_ = ( lParam ? LoginStatus::logged_in : LoginStatus::logged_out ); UpdateLibSpotifyUi(); UpdateWebApiUi(); return 0; } void PreferenceTabAuth::UpdateUiFromCfg() { if ( !this->m_hWnd ) { return; } for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Ddx().WriteToUi(); } } void PreferenceTabAuth::UpdateLibSpotifyUi() { const auto getUsername = [] { return SpotifyInstance::Get().GetLibSpotify_Backend().GetLoggedInUserName(); }; UpdateBackendUi( libSpotifyStatus_, btnLibSpotify_, textLibSpotify_, getUsername ); } void PreferenceTabAuth::UpdateWebApiUi() { const auto getUsername = [] { qwr::TimedAbortCallback tac( fmt::format( "{}: {}", SPTF_UNDERSCORE_NAME, "WebApi get username" ) ); try { auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend(); const auto pUser = waBackend.GetUser( tac ); return pUser->display_name.value_or( pUser->uri ); } catch ( const std::exception& e ) { return fmt::format( "", e.what() ); } }; UpdateBackendUi( webApiStatus_, btnWebApi_, textWebApi_, getUsername ); } void PreferenceTabAuth::UpdateBackendUi( LoginStatus loginStatus, CButton& btn, CStatic& text, std::function getUserNameFn ) { Invalidate(); ///< needed to clear static text switch ( loginStatus ) { case LoginStatus::fetching_login_status: { if ( btn.IsWindowEnabled() ) { btn.EnableWindow( FALSE ); } btn.SetWindowText( L"Log in" ); text.SetWindowText( L"status: verifying login status" ); break; } case LoginStatus::logged_out: { if ( !btn.IsWindowEnabled() ) { btn.EnableWindow( TRUE ); } btn.SetWindowText( L"Log in" ); text.SetWindowText( L"status: logged out" ); break; } case LoginStatus::login_in_progress: { if ( btn.IsWindowEnabled() ) { btn.EnableWindow( FALSE ); } btn.SetWindowText( L"Log in" ); text.SetWindowText( L"status: login in progress..." ); break; } case LoginStatus::logged_in: { if ( !btn.IsWindowEnabled() ) { btn.EnableWindow( TRUE ); } btn.SetWindowText( L"Log out" ); text.SetWindowText( qwr::unicode::ToWide( fmt::format( "status: logged in as `{}`", getUserNameFn() ) ).c_str() ); break; } default: break; } } } // namespace sptf::ui ================================================ FILE: foo_spotify/ui/ui_pref_tab_auth.h ================================================ #pragma once #include #include #include #include #include #include #include namespace sptf::ui { class PreferenceTabManager; class PreferenceTabAuth : public CDialogImpl , public IPrefTab { private: enum class LoginStatus { fetching_login_status, logged_out, login_in_progress, logged_in, }; public: enum { IDD = IDD_PREF_TAB_AUTH }; PreferenceTabAuth( PreferenceTabManager* pParent ); ~PreferenceTabAuth() override; // IPrefTab HWND CreateTab( HWND hParent ) override; CDialogImplBase& Dialog() override; const wchar_t* Name() const override; t_uint32 get_state() override; void apply() override; void reset() override; protected: static constexpr int kOnWebApiLoginResponse = WM_USER + 1; static constexpr int kOnStatusUpdateFinish = WM_USER + 2; BEGIN_MSG_MAP( PreferenceTabAuth ) MSG_WM_INITDIALOG( OnInitDialog ) MSG_WM_DESTROY( OnDestroy ) MSG_WM_CTLCOLORSTATIC( OnCtlColorStatic ) COMMAND_HANDLER_EX( IDC_BTN_LOGIN_LIBSPOTIFY, BN_CLICKED, OnLibSpotifyLoginClick ) COMMAND_HANDLER_EX( IDC_BTN_LOGIN_WEBAPI, BN_CLICKED, OnWebApiLoginClick ) MESSAGE_HANDLER( kOnWebApiLoginResponse, OnWebApiLoginResponse ) MESSAGE_HANDLER( kOnStatusUpdateFinish, OnStatusUpdateFinish ) END_MSG_MAP() private: BOOL OnInitDialog( HWND hwndFocus, LPARAM lParam ); void OnDestroy(); HBRUSH OnCtlColorStatic( CDCHandle dc, CStatic wndStatic ); void OnDdxChange( UINT uNotifyCode, int nID, CWindow wndCtl ); void OnLibSpotifyLoginClick( UINT uNotifyCode, int nID, CWindow wndCtl ); void OnWebApiLoginClick( UINT uNotifyCode, int nID, CWindow wndCtl ); LRESULT OnWebApiLoginResponse( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnStatusUpdateFinish( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); private: void UpdateUiFromCfg(); void UpdateLibSpotifyUi(); void UpdateWebApiUi(); void UpdateBackendUi( LoginStatus loginStatus, CButton& btn, CStatic& text, std::function getUserNameFn ); private: PreferenceTabManager* pParent_ = nullptr; //qwr::ui::UiOption preferred_bitrate_; std::array, 0> ddxOptions_; CButton btnLibSpotify_; CButton btnWebApi_; CStatic textLibSpotify_; CStatic textWebApi_; LoginStatus libSpotifyStatus_ = LoginStatus::fetching_login_status; LoginStatus webApiStatus_ = LoginStatus::fetching_login_status; std::unique_ptr pStatusThread_; }; } // namespace sptf::ui ================================================ FILE: foo_spotify/ui/ui_pref_tab_manager.cpp ================================================ #include #include "ui_pref_tab_manager.h" #include #include #include #include namespace { using namespace sptf; class preferences_page_impl : public preferences_page_v3 { public: const char* get_name() override { return SPTF_NAME; } GUID get_guid() override { return guid::preferences; } GUID get_parent_guid() override { return preferences_page::guid_tools; } bool get_help_url( pfc::string_base& p_out ) override { p_out << qwr::unicode::ToU8( std::wstring_view( url::homepage ) ); return true; } preferences_page_instance::ptr instantiate( HWND parent, preferences_page_callback::ptr callback ) override { auto p = ::fb2k::service_new( callback ); p->Create( parent ); return p; } }; preferences_page_factory_t g_pref; } // namespace namespace sptf::ui { using namespace config; PreferenceTabManager::PreferenceTabManager( preferences_page_callback::ptr callback ) : callback_( callback ) , tabs_( { std::make_unique( this ), std::make_unique( this ) } ) { } void PreferenceTabManager::OnDataChanged() { callback_->on_state_changed(); } HWND PreferenceTabManager::get_wnd() { return m_hWnd; } t_uint32 PreferenceTabManager::get_state() { uint32_t state = preferences_state::resettable; for ( auto& tab: tabs_ ) { state |= tab->get_state(); } return state; } void PreferenceTabManager::apply() { for ( auto& tab: tabs_ ) { tab->apply(); } OnDataChanged(); } void PreferenceTabManager::reset() { for ( auto& tab: tabs_ ) { tab->reset(); } OnDataChanged(); } BOOL PreferenceTabManager::OnInitDialog( HWND hwndFocus, LPARAM lParam ) { cTabs_ = GetDlgItem( IDC_TAB_HOST ); for ( const auto& [i, pTab]: ranges::views::enumerate( tabs_ ) ) { cTabs_.InsertItem( i, pTab->Name() ); } cTabs_.SetCurSel( activeTabIdx_ ); CreateTab(); return TRUE; // set focus to default control } void PreferenceTabManager::OnParentNotify( UINT message, UINT nChildID, LPARAM lParam ) { if ( WM_DESTROY == message && pcCurTab_ && reinterpret_cast( lParam ) == static_cast( *pcCurTab_ ) ) { pcCurTab_ = nullptr; } } LRESULT PreferenceTabManager::OnSelectionChanged( LPNMHDR pNmhdr ) { activeTabIdx_ = TabCtrl_GetCurSel( GetDlgItem( IDC_TAB_HOST ) ); CreateTab(); return 0; } LRESULT PreferenceTabManager::OnWindowPosChanged( UINT, WPARAM, LPARAM lp, BOOL& bHandled ) { auto lpwp = reinterpret_cast( lp ); // Temporary workaround for various bugs that occur due to foobar2000 1.0+ // having a dislike for destroying preference pages if ( lpwp->flags & SWP_HIDEWINDOW ) { DestroyTab(); } else if ( lpwp->flags & SWP_SHOWWINDOW && !pcCurTab_ ) { CreateTab(); } bHandled = FALSE; return 0; } void PreferenceTabManager::CreateTab() { DestroyTab(); RECT tabRc; cTabs_.GetWindowRect( &tabRc ); ::MapWindowPoints( HWND_DESKTOP, m_hWnd, (LPPOINT)&tabRc, 2 ); cTabs_.AdjustRect( FALSE, &tabRc ); if ( activeTabIdx_ >= tabs_.size() ) { activeTabIdx_ = 0; } auto& pCurTab = tabs_[activeTabIdx_]; pcCurTab_ = &pCurTab->Dialog(); pCurTab->CreateTab( m_hWnd ); EnableThemeDialogTexture( *pcCurTab_, ETDT_ENABLETAB ); pcCurTab_->SetWindowPos( nullptr, tabRc.left, tabRc.top, tabRc.right - tabRc.left, tabRc.bottom - tabRc.top, SWP_NOZORDER ); cTabs_.SetWindowPos( HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE ); pcCurTab_->ShowWindow( SW_SHOWNORMAL ); } void PreferenceTabManager::DestroyTab() { if ( pcCurTab_ && *pcCurTab_ ) { pcCurTab_->ShowWindow( SW_HIDE ); pcCurTab_->DestroyWindow(); } } } // namespace sptf::ui ================================================ FILE: foo_spotify/ui/ui_pref_tab_manager.h ================================================ #pragma once #include #include #include namespace sptf::ui { class PreferenceTabMain; class PreferenceTabManager : public CDialogImpl , public CWinDataExchange , public preferences_page_instance { public: enum { IDD = IDD_PREF_TAB_HOST }; BEGIN_MSG_MAP( PreferenceTabManager ) MSG_WM_INITDIALOG( OnInitDialog ) MSG_WM_PARENTNOTIFY( OnParentNotify ) MESSAGE_HANDLER( WM_WINDOWPOSCHANGED, OnWindowPosChanged ) NOTIFY_HANDLER_EX( IDC_TAB_HOST, TCN_SELCHANGE, OnSelectionChanged ) END_MSG_MAP() public: PreferenceTabManager( preferences_page_callback::ptr callback ); void OnDataChanged(); // preferences_page_instance HWND get_wnd() override; t_uint32 get_state() override; void apply() override; void reset() override; private: BOOL OnInitDialog( HWND hwndFocus, LPARAM lParam ); void OnParentNotify( UINT message, UINT nChildID, LPARAM lParam ); LRESULT OnWindowPosChanged( UINT, WPARAM, LPARAM lp, BOOL& bHandled ); LRESULT OnSelectionChanged( LPNMHDR pNmhdr ); void CreateTab(); void DestroyTab(); private: preferences_page_callback::ptr callback_; CTabCtrl cTabs_; CDialogImplBase* pcCurTab_ = nullptr; size_t activeTabIdx_ = 0; std::array, 2> tabs_; }; } // namespace sptf::ui ================================================ FILE: foo_spotify/ui/ui_pref_tab_playback.cpp ================================================ #include #include "ui_pref_tab_playback.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { int GetNearestScrollPos( float curValue, int direction ) { if ( auto curShift = curValue - std::floor( curValue ); !curShift ) { return (int)( curValue + direction ); } else { if ( direction < 0 ) { return (int)std::floor( curValue ); } else if ( direction > 0 ) { return (int)std::ceil( curValue ); } else { return (int)std::round( curValue ); } } } } // namespace namespace sptf::ui { PreferenceTabPlayback::PreferenceTabPlayback( PreferenceTabManager* pParent ) : pParent_( pParent ) , trackToEdit_( { { IDC_SLIDER_CACHE_PERCENT, IDC_EDIT_CACHE_PERCENT }, { IDC_SLIDER_CACHE_MB, IDC_EDIT_CACHE_MB } } ) , editToTrack_( { { IDC_EDIT_CACHE_PERCENT, IDC_SLIDER_CACHE_PERCENT }, { IDC_EDIT_CACHE_MB, IDC_SLIDER_CACHE_MB } } ) , enable_normalization_( config::enable_normalization ) , enable_private_mode_( config::enable_private_mode ) , libspotify_cache_size_in_percent_( config::libspotify_cache_size_in_percent ) , libspotify_cache_size_in_mb_( config::libspotify_cache_size_in_mb ) , preferred_bitrate_( config::preferred_bitrate, { { config::BitrateSettings::Bitrate96k, 0 }, { config::BitrateSettings::Bitrate160k, 1 }, { config::BitrateSettings::Bitrate320k, 2 } } ) , ddxOptions_( { qwr::ui::CreateUiDdxOption( enable_normalization_, IDC_CHECK_NORMALIZE ), qwr::ui::CreateUiDdxOption( enable_private_mode_, IDC_CHECK_PRIVATE ), qwr::ui::CreateUiDdxOption( libspotify_cache_size_in_percent_, IDC_SLIDER_CACHE_PERCENT ), qwr::ui::CreateUiDdxOption( libspotify_cache_size_in_mb_, IDC_SLIDER_CACHE_MB ), qwr::ui::CreateUiDdxOption( preferred_bitrate_, IDC_COMBO_BITRATE ) } ) { } PreferenceTabPlayback::~PreferenceTabPlayback() { for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Option().Revert(); } } HWND PreferenceTabPlayback::CreateTab( HWND hParent ) { return Create( hParent ); } CDialogImplBase& PreferenceTabPlayback::Dialog() { return *this; } const wchar_t* PreferenceTabPlayback::Name() const { return L"Playback"; } t_uint32 PreferenceTabPlayback::get_state() { const bool hasChanged = ddxOptions_.cend() != std::find_if( ddxOptions_.cbegin(), ddxOptions_.cend(), []( const auto& ddxOpt ) { return ddxOpt->Option().HasChanged(); } ); return ( preferences_state::resettable | ( hasChanged ? preferences_state::changed : 0 ) ); } void PreferenceTabPlayback::apply() { for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Option().Apply(); } RefreshLibSpotifySettings(); pParent_->OnDataChanged(); } void PreferenceTabPlayback::reset() { for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Option().ResetToDefault(); } DoFullDdxToUi(); pParent_->OnDataChanged(); } BOOL PreferenceTabPlayback::OnInitDialog( HWND hwndFocus, LPARAM lParam ) { CComboBox comboBitrate( GetDlgItem( IDC_COMBO_BITRATE ) ); comboBitrate.AddString( L"96 kbit/s" ); comboBitrate.AddString( L"160 kbit/s" ); comboBitrate.AddString( L"320 kbit/s" ); CTrackBarCtrl trackCacheMb( GetDlgItem( IDC_SLIDER_CACHE_MB ) ); trackCacheMb.SetRangeMin( 0 ); trackCacheMb.SetRangeMax( 8192 ); trackCacheMb.SetTicFreq( 512 ); CTrackBarCtrl trackCachePercent( GetDlgItem( IDC_SLIDER_CACHE_PERCENT ) ); trackCachePercent.SetRangeMin( 0 ); trackCachePercent.SetRangeMax( 100 ); trackCachePercent.SetTicFreq( 5 ); for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Ddx().SetHwnd( m_hWnd ); } DoFullDdxToUi(); this->SetDlgItemInt( IDC_EDIT_CACHE_MB, libspotify_cache_size_in_mb_, false ); this->SetDlgItemInt( IDC_EDIT_CACHE_PERCENT, libspotify_cache_size_in_percent_, false ); suppressUiDdx_ = false; return TRUE; // set focus to default control } void PreferenceTabPlayback::OnDdxUiChange( UINT uNotifyCode, int nID, CWindow wndCtl ) { if ( suppressUiDdx_ ) { return; } auto it = std::find_if( ddxOptions_.begin(), ddxOptions_.end(), [nID]( auto& val ) { return val->Ddx().IsMatchingId( nID ); } ); if ( ddxOptions_.end() != it ) { ( *it )->Ddx().ReadFromUi(); pParent_->OnDataChanged(); } } void PreferenceTabPlayback::OnDdxValueChange( int nID ) { // avoid triggering loopback ddx suppressUiDdx_ = true; const qwr::final_action autoSuppress( [&] { suppressUiDdx_ = false; } ); auto it = std::find_if( ddxOptions_.begin(), ddxOptions_.end(), [nID]( auto& val ) { return val->Ddx().IsMatchingId( nID ); } ); assert( ddxOptions_.end() != it ); ( *it )->Ddx().WriteToUi(); } LRESULT PreferenceTabPlayback::OnTrackBarPosChangedNotify( LPNMHDR pnmh ) { auto pNMTR = reinterpret_cast( pnmh ); if ( pNMTR->nReason == TB_ENDTRACK ) { return 1; } if ( suppressUiDdx_ ) { return 1; } const auto id = pNMTR->hdr.idFrom; CTrackBarCtrl trackBar( GetDlgItem( id ) ); const auto tickCount = trackBar.GetNumTics() - 1; const auto rangeMin = trackBar.GetRangeMin(); const auto rangeMax = trackBar.GetRangeMax(); const auto tickFreq = ( rangeMax - rangeMin ) / tickCount; const auto prevPos = trackBar.GetPos(); const auto pos = pNMTR->dwPos; auto adjustedPos = [&] { const auto ticks = (float)pos / tickFreq; const auto diffSign = ( (int)pos - (int)prevPos < 0 ? -1 : 1 ); switch ( pNMTR->nReason ) { case TB_PAGEUP: case TB_PAGEDOWN: { const auto diffTicks = std::abs( (int)pos - (int)prevPos ) / (int)tickFreq; return ( GetNearestScrollPos( ticks, diffSign ) + diffSign * diffTicks ) * (int)tickFreq; } case TB_LINEUP: case TB_LINEDOWN: { return GetNearestScrollPos( ticks, diffSign ) * (int)tickFreq; } case TB_THUMBTRACK: case TB_THUMBPOSITION: { // snap to the closest return GetNearestScrollPos( ticks, 0 ) * (int)tickFreq; } default: { return (int)pos; } } }(); adjustedPos = std::clamp( adjustedPos, rangeMin, rangeMax ); tracksRedrawing_.emplace( id ); trackBar.SetPos( adjustedPos ); OnDdxUiChange( 0, id, nullptr ); // avoid triggering ddx for linked edit suppressUiDdx_ = true; const qwr::final_action autoSuppress( [&] { suppressUiDdx_ = false; } ); this->SetDlgItemInt( trackToEdit_.at( id ), adjustedPos, false ); return 1; } void PreferenceTabPlayback::OnTrackBarHScroll( UINT nSBCode, UINT nPos, CTrackBarCtrl trackBar ) { if ( nSBCode == TB_ENDTRACK ) { return; } const auto trackId = trackBar.GetDlgCtrlID(); if ( tracksRedrawing_.count( trackId ) ) { tracksRedrawing_.erase( trackId ); return; } // some events are not processed by TRBN_THUMBPOSCHANGING const auto pos = trackBar.GetPos(); OnDdxUiChange( 0, trackId, nullptr ); // avoid triggering ddx for linked edit suppressUiDdx_ = true; const qwr::final_action autoSuppress( [&] { suppressUiDdx_ = false; } ); this->SetDlgItemInt( trackToEdit_.at( trackId ), pos, false ); } void PreferenceTabPlayback::OnTrackBarEdit( UINT uNotifyCode, int nID, CWindow wndCtl ) { if ( suppressUiDdx_ ) { return; } BOOL lpTrans; const auto editPosRaw = this->GetDlgItemInt( nID, &lpTrans, false ); if ( !lpTrans ) { return; } const auto trackId = editToTrack_.at( nID ); CTrackBarCtrl trackbar( GetDlgItem( trackId ) ); const auto editPos = std::clamp( editPosRaw, trackbar.GetRangeMin(), trackbar.GetRangeMax() ); if ( editPos != editPosRaw ) { this->SetDlgItemInt( nID, editPos, false ); } if ( trackbar.GetPos() == editPos ) { return; } switch ( trackId ) { case IDC_SLIDER_CACHE_MB: libspotify_cache_size_in_mb_ = editPos; break; case IDC_SLIDER_CACHE_PERCENT: libspotify_cache_size_in_percent_ = editPos; break; default: assert( false ); break; } OnDdxValueChange( trackId ); } void PreferenceTabPlayback::DoFullDdxToUi() { if ( !this->m_hWnd ) { return; } for ( auto& ddxOpt: ddxOptions_ ) { ddxOpt->Ddx().WriteToUi(); } } void PreferenceTabPlayback::RefreshLibSpotifySettings() { auto& lsBackend = SpotifyInstance::Get().GetLibSpotify_Backend(); lsBackend.RefreshBitrate(); lsBackend.RefreshNormalization(); lsBackend.RefreshPrivateMode(); lsBackend.RefreshCacheSize(); } } // namespace sptf::ui ================================================ FILE: foo_spotify/ui/ui_pref_tab_playback.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include namespace sptf::ui { class PreferenceTabManager; class PreferenceTabPlayback : public CDialogImpl , public IPrefTab { public: enum { IDD = IDD_PREF_TAB_PLAYBACK }; PreferenceTabPlayback( PreferenceTabManager* pParent ); ~PreferenceTabPlayback() override; // IPrefTab HWND CreateTab( HWND hParent ) override; CDialogImplBase& Dialog() override; const wchar_t* Name() const override; t_uint32 get_state() override; void apply() override; void reset() override; protected: BEGIN_MSG_MAP( PreferenceTabPlayback ) MSG_WM_INITDIALOG( OnInitDialog ) MSG_WM_HSCROLL( OnTrackBarHScroll ) COMMAND_HANDLER_EX( IDC_COMBO_BITRATE, CBN_SELCHANGE, OnDdxUiChange ) COMMAND_HANDLER_EX( IDC_CHECK_NORMALIZE, BN_CLICKED, OnDdxUiChange ) COMMAND_HANDLER_EX( IDC_CHECK_PRIVATE, BN_CLICKED, OnDdxUiChange ) NOTIFY_HANDLER_EX( IDC_SLIDER_CACHE_MB, TRBN_THUMBPOSCHANGING, OnTrackBarPosChangedNotify ) NOTIFY_HANDLER_EX( IDC_SLIDER_CACHE_PERCENT, TRBN_THUMBPOSCHANGING, OnTrackBarPosChangedNotify ) COMMAND_HANDLER_EX( IDC_EDIT_CACHE_MB, EN_CHANGE, OnTrackBarEdit ) COMMAND_HANDLER_EX( IDC_EDIT_CACHE_PERCENT, EN_CHANGE, OnTrackBarEdit ) END_MSG_MAP() private: BOOL OnInitDialog( HWND hwndFocus, LPARAM lParam ); void OnDdxUiChange( UINT uNotifyCode, int nID, CWindow wndCtl ); void OnDdxValueChange( int nID ); LRESULT OnTrackBarPosChangedNotify( LPNMHDR pnmh ); void OnTrackBarHScroll( UINT nSBCode, UINT nPos, CTrackBarCtrl trackBar ); void OnTrackBarEdit( UINT uNotifyCode, int nID, CWindow wndCtl ); void DoFullDdxToUi(); void RefreshLibSpotifySettings(); private: PreferenceTabManager* pParent_ = nullptr; std::unordered_map trackToEdit_; std::unordered_map editToTrack_; std::unordered_set tracksRedrawing_; bool suppressUiDdx_ = true; #define SPTF_DEFINE_UI_OPTION( name ) \ qwr::ui::UiOption name##_; #define SPTF_DEFINE_UI_OPTIONS( ... ) \ QWR_EXPAND( QWR_PASTE( SPTF_DEFINE_UI_OPTION, __VA_ARGS__ ) ) SPTF_DEFINE_UI_OPTIONS( enable_normalization, enable_private_mode, libspotify_cache_size_in_percent, libspotify_cache_size_in_mb, preferred_bitrate ) #undef SPTF_DEFINE_OPTIONS #undef SPTF_DEFINE_OPTION std::array, 5> ddxOptions_; }; } // namespace sptf::ui ================================================ FILE: foo_spotify/utils/abort_manager.cpp ================================================ #include #include "abort_manager.h" #include namespace sptf { AbortManager::AbortManager() { StartThread(); } void AbortManager::Finalize() { StopThread(); } void AbortManager::RemoveTask( uint32_t taskId ) { std::unique_lock lock( mutex_ ); const auto itTask = idToTask_.find( taskId ); assert( itTask != idToTask_.cend() ); const auto pAbort = itTask->second.pAbort; idToTask_.erase( itTask ); const auto itIds = abortToIds_.find( pAbort ); assert( itIds != abortToIds_.cend() ); auto& ids = itIds->second; const auto itId = std::find( ids.rbegin(), ids.rend(), taskId ); assert( itId != ids.rend() ); ids.erase( std::next( itId ).base() ); if ( ids.empty() ) { abortToIds_.erase( itIds ); } assert( abortToIds_.size() <= idToTask_.size() ); } void AbortManager::StartThread() { pThread_ = std::make_unique( &AbortManager::EventLoop, this ); qwr::SetThreadName( *pThread_, "SPTF Abort Handler" ); } void AbortManager::StopThread() { if ( !pThread_ ) { return; } { std::unique_lock lock( mutex_ ); isTimeToDie_ = true; } eventCv_.notify_all(); if ( pThread_->joinable() ) { pThread_->join(); } pThread_.reset(); } void AbortManager::EventLoop() { while ( true ) { std::unique_lock lock( mutex_ ); do { eventCv_.wait_for( lock, std::chrono::seconds( 2 ) ); } while ( !isTimeToDie_ && abortToIds_.empty() ); if ( isTimeToDie_ ) { for ( auto& [id, task]: idToTask_ ) { std::invoke( *( task.task ) ); } return; } for ( const auto& [pAbort, ids]: abortToIds_ ) { if ( pAbort->is_aborting() ) { for ( const auto& id: ranges::views::reverse( ids ) ) { assert( idToTask_.count( id ) ); std::invoke( *( idToTask_[id].task ) ); } } } } } AbortManager::AbortableScope::AbortableScope( size_t taskId, AbortManager& parent ) : parent_( parent ) , taskId_( taskId ) { } AbortManager::AbortableScope::AbortableScope( AbortableScope&& other ) : parent_( other.parent_ ) , isValid_( other.isValid_ ) , taskId_( other.taskId_ ) { other.isValid_ = false; } AbortManager::AbortableScope::~AbortableScope() { if ( isValid_ ) { parent_.RemoveTask( taskId_ ); } } } // namespace sptf ================================================ FILE: foo_spotify/utils/abort_manager.h ================================================ #pragma once #include #include #include namespace sptf { class AbortManager { private: using Task = std::function; struct AbortableTask { std::unique_ptr task; abort_callback* pAbort; }; public: class AbortableScope { friend class AbortManager; public: AbortableScope( AbortableScope&& other ); ~AbortableScope(); private: AbortableScope( size_t taskId, AbortManager& parent ); private: AbortManager& parent_; bool isValid_ = true; size_t taskId_; }; public: AbortManager(); AbortManager( const AbortManager& other ) = delete; AbortManager( AbortManager&& other ) = delete; ~AbortManager() = default; void Finalize(); template AbortableScope GetAbortableScope( T&& task, abort_callback& abort ) { return AbortableScope( AddTask( std::forward( task ), abort ), *this ); } private: void StartThread(); void StopThread(); void EventLoop(); template size_t AddTask( T&& task, abort_callback& abort ) { static_assert( std::is_invocable_v ); static_assert( std::is_move_constructible_v || std::is_copy_constructible_v ); const auto taskId = [&] { std::unique_lock lock( mutex_ ); const auto taskId = [&] { auto id = idCounter_; while ( idToTask_.count( id ) ) { id = ++idCounter_; } return id; }(); if constexpr ( !std::is_copy_constructible_v && std::is_move_constructible_v ) { auto taskLambda = [taskWrapper = std::make_shared( std::forward( task ) )] { std::invoke( *taskWrapper ); }; idToTask_.try_emplace( taskId, AbortableTask{ std::make_unique( taskLambda ), &abort } ); } else { idToTask_.try_emplace( taskId, AbortableTask{ std::make_unique( std::forward( task ) ), &abort } ); } return abortToIds_[&abort].emplace_back( taskId ); }(); eventCv_.notify_all(); return taskId; } void RemoveTask( uint32_t taskId ); private: std::unique_ptr pThread_; std::mutex mutex_; std::condition_variable eventCv_; bool isTimeToDie_ = false; size_t idCounter_ = 0; std::unordered_map idToTask_; std::unordered_map> abortToIds_; }; } // namespace sptf ================================================ FILE: foo_spotify/utils/async_mutex.hpp ================================================ /////////////////////////////////////////////////////////////////////////////// // Copyright (c) Lewis Baker // Licenced under MIT license. See LICENSE.txt for details. /////////////////////////////////////////////////////////////////////////////// #ifndef CPPCORO_ASYNC_MUTEX_HPP_INCLUDED #define CPPCORO_ASYNC_MUTEX_HPP_INCLUDED #include #include #include // for std::adopt_lock_t #include namespace cppcoro { class async_mutex_lock; class async_mutex_lock_operation; class async_mutex_scoped_lock_operation; /// \brief /// A mutex that can be locked asynchronously using 'co_await'. /// /// Ownership of the mutex is not tied to any particular thread. /// This allows the coroutine owning the lock to transition from /// one thread to another while holding a lock. /// /// Implementation is lock-free, using only std::atomic values for /// synchronisation. Awaiting coroutines are suspended without blocking /// the current thread if the lock could not be acquired synchronously. class async_mutex { public: /// \brief /// Construct to a mutex that is not currently locked. async_mutex() noexcept; /// Destroys the mutex. /// /// Behaviour is undefined if there are any outstanding coroutines /// still waiting to acquire the lock. ~async_mutex(); /// \brief /// Attempt to acquire a lock on the mutex without blocking. /// /// \return /// true if the lock was acquired, false if the mutex was already locked. /// The caller is responsible for ensuring unlock() is called on the mutex /// to release the lock if the lock was acquired by this call. bool try_lock() noexcept; /// \brief /// Acquire a lock on the mutex asynchronously. /// /// If the lock could not be acquired synchronously then the awaiting /// coroutine will be suspended and later resumed when the lock becomes /// available. If suspended, the coroutine will be resumed inside the /// call to unlock() from the previous lock owner. /// /// \return /// An operation object that must be 'co_await'ed to wait until the /// lock is acquired. The result of the 'co_await m.lock_async()' /// expression has type 'void'. async_mutex_lock_operation lock_async() noexcept; /// \brief /// Acquire a lock on the mutex asynchronously, returning an object that /// will call unlock() automatically when it goes out of scope. /// /// If the lock could not be acquired synchronously then the awaiting /// coroutine will be suspended and later resumed when the lock becomes /// available. If suspended, the coroutine will be resumed inside the /// call to unlock() from the previous lock owner. /// /// \return /// An operation object that must be 'co_await'ed to wait until the /// lock is acquired. The result of the 'co_await m.scoped_lock_async()' /// expression returns an 'async_mutex_lock' object that will call /// this->mutex() when it destructs. async_mutex_scoped_lock_operation scoped_lock_async() noexcept; /// \brief /// Unlock the mutex. /// /// Must only be called by the current lock-holder. /// /// If there are lock operations waiting to acquire the /// mutex then the next lock operation in the queue will /// be resumed inside this call. void unlock(); private: friend class async_mutex_lock_operation; static constexpr std::uintptr_t not_locked = 1; // assume == reinterpret_cast(static_cast(nullptr)) static constexpr std::uintptr_t locked_no_waiters = 0; // This field provides synchronisation for the mutex. // // It can have three kinds of values: // - not_locked // - locked_no_waiters // - a pointer to the head of a singly linked list of recently // queued async_mutex_lock_operation objects. This list is // in most-recently-queued order as new items are pushed onto // the front of the list. std::atomic m_state; // Linked list of async lock operations that are waiting to acquire // the mutex. These operations will acquire the lock in the order // they appear in this list. Waiters in this list will acquire the // mutex before waiters added to the m_newWaiters list. async_mutex_lock_operation* m_waiters; }; /// \brief /// An object that holds onto a mutex lock for its lifetime and /// ensures that the mutex is unlocked when it is destructed. /// /// It is equivalent to a std::lock_guard object but requires /// that the result of co_await async_mutex::lock_async() is /// passed to the constructor rather than passing the async_mutex /// object itself. class async_mutex_lock { public: explicit async_mutex_lock( async_mutex& mutex, std::adopt_lock_t ) noexcept : m_mutex( &mutex ) { } async_mutex_lock( async_mutex_lock&& other ) noexcept : m_mutex( other.m_mutex ) { other.m_mutex = nullptr; } async_mutex_lock( const async_mutex_lock& other ) = delete; async_mutex_lock& operator=( const async_mutex_lock& other ) = delete; // Releases the lock. ~async_mutex_lock() { if ( m_mutex != nullptr ) { m_mutex->unlock(); } } private: async_mutex* m_mutex; }; class async_mutex_lock_operation { public: explicit async_mutex_lock_operation( async_mutex& mutex ) noexcept : m_mutex( mutex ) { } bool await_ready() const noexcept { return false; } bool await_suspend( std::experimental::coroutine_handle<> awaiter ) noexcept; void await_resume() const noexcept { } protected: friend class async_mutex; async_mutex& m_mutex; private: async_mutex_lock_operation* m_next; std::experimental::coroutine_handle<> m_awaiter; }; class async_mutex_scoped_lock_operation : public async_mutex_lock_operation { public: using async_mutex_lock_operation::async_mutex_lock_operation; [[nodiscard]] async_mutex_lock await_resume() const noexcept { return async_mutex_lock{ m_mutex, std::adopt_lock }; } }; } // namespace cppcoro #endif ================================================ FILE: foo_spotify/utils/cred_prompt.cpp ================================================ #include #include "cred_prompt.h" #include #include namespace sptf { std::unique_ptr ShowCredentialsDialog( HWND hWnd, const char* msg ) { const auto wMsg = [msg] { if (msg) { auto ret = qwr::unicode::ToWide( std::string_view{ msg } ); if ( ret.size() > CREDUI_MAX_MESSAGE_LENGTH ) { ret.resize( CREDUI_MAX_MESSAGE_LENGTH ); ret[CREDUI_MAX_MESSAGE_LENGTH - 3] = L'.'; ret[CREDUI_MAX_MESSAGE_LENGTH - 2] = L'.'; ret[CREDUI_MAX_MESSAGE_LENGTH - 1] = L'.'; } return ret; } else { return std::wstring( L"Please enter your Spotify username and password." ); } }(); CREDUI_INFOW cui{}; cui.cbSize = sizeof( CREDUI_INFO ); cui.hwndParent = hWnd; // Ensure that MessageText and CaptionText identify what credentials // to use and which application requires them. cui.pszMessageText = wMsg.c_str(); cui.pszCaptionText = L"Sign in to Spotify"; BOOL fSave = FALSE; SecureVector pszName( CREDUI_MAX_USERNAME_LENGTH + 1 ); SecureVector pszPwd( CREDUI_MAX_PASSWORD_LENGTH + 1 ); const DWORD dwErr = CredUIPromptForCredentialsW( &cui, // CREDUI_INFO structure TEXT( SPTF_UNDERSCORE_NAME ), // Target for credentials nullptr, // Reserved 0, // Reason pszName.data(), // User name pszName.size(), // Max number of char for user name pszPwd.data(), // Password pszPwd.size(), // Max number of char for password &fSave, // State of save check box CREDUI_FLAGS_GENERIC_CREDENTIALS | // flags CREDUI_FLAGS_ALWAYS_SHOW_UI | CREDUI_FLAGS_DO_NOT_PERSIST ); auto cpr = std::make_unique(); if ( dwErr == NO_ERROR ) { const auto wtou = []( const auto& in, auto& out ) { size_t stringLen = WideCharToMultiByte( CP_UTF8, 0, in.data(), in.size(), nullptr, 0, nullptr, nullptr ); out.resize( stringLen ); stringLen = WideCharToMultiByte( CP_UTF8, 0, in.data(), in.size(), out.data(), out.size(), nullptr, nullptr ); out.resize( stringLen ); }; wtou( pszName, cpr->un ); wtou( pszPwd, cpr->pw ); } else if ( dwErr == ERROR_CANCELLED ) { cpr->cancelled = true; } return cpr; } } // namespace sptf ================================================ FILE: foo_spotify/utils/cred_prompt.h ================================================ #pragma once #include namespace sptf { struct CredentialsResult { SecureVector un; SecureVector pw; bool cancelled = false; }; std::unique_ptr ShowCredentialsDialog( HWND hWnd, const char* msg ); } // namespace sptf ================================================ FILE: foo_spotify/utils/json_macro_fix.h ================================================ #pragma once // same as the stock one, but without `inline` #define SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( Type, ... ) \ void to_json( nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t ) \ { \ NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_TO, __VA_ARGS__ ) ) \ } \ void from_json( const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t ) \ { \ NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_FROM, __VA_ARGS__ ) ) \ } ================================================ FILE: foo_spotify/utils/json_std_extenders.h ================================================ #include namespace nlohmann { template struct adl_serializer> { static void to_json( nlohmann::json& j, const std::unique_ptr& value ) { if ( !value ) { throw nlohmann::json::type_error::create( 302, "type must not be null, but is" ); } j = *value; } static void from_json( const nlohmann::json& j, std::unique_ptr& p ) { if ( j.is_null() ) { throw nlohmann::json::type_error::create( 302, "type must not be null, but is" ); } p = std::make_unique( j.get() ); } }; template struct adl_serializer> { static void to_json( nlohmann::json& j, const std::shared_ptr& value ) { if ( !value ) { throw nlohmann::json::type_error::create( 302, "type must not be null, but is" ); } j = *value; } static void from_json( const nlohmann::json& j, std::shared_ptr& p ) { if ( j.is_null() ) { throw nlohmann::json::type_error::create( 302, "type must not be null, but is" ); } p = std::make_shared( j.get() ); } }; template struct adl_serializer> { static void to_json( nlohmann::json& j, const std::optional& value ) { if ( !value ) { j = nlohmann::json(); } else { j = *value; } } static void from_json( const nlohmann::json& j, std::optional& p ) { if ( j.is_null() ) { p = std::nullopt; } else { p = j.get(); } } }; template <> struct adl_serializer { static void to_json( json& j, const std::wstring& value ) { j = qwr::unicode::ToU8( value ); } static void from_json( const json& j, std::wstring& value ) { value = qwr::unicode::ToWide( j.get() ); } }; } // namespace nlohmann ================================================ FILE: foo_spotify/utils/rps_limiter.cpp ================================================ #include #include "rps_limiter.h" #include #include #include #include #include namespace { std::chrono::milliseconds GetTimestampInMs() { return std::chrono::time_point_cast( std::chrono::system_clock::now() ).time_since_epoch(); } } // namespace namespace sptf { RpsLimiter::RpsLimiter( size_t limit, std::chrono::seconds limitPeriod ) : shouldLogWebApiDebug_( config::advanced::logging_webapi_debug ) , limitPeriod_( limitPeriod ) , limitCount_( limit ) , timeStampsContainer_( limit, GetTimestampInMs() - 2 * limitPeriod ) , timeStamps_( timeStampsContainer_.begin(), timeStampsContainer_.end(), timeStampsContainer_.begin(), timeStampsContainer_.size() ) { } void RpsLimiter::WaitForRequestAvailability( abort_callback& abort ) { if ( abort.is_aborting() ) { return; } std::atomic_bool timeToDie = false; auto& am = SpotifyInstance::Get().GetAbortManager(); const auto abortableScope = am.GetAbortableScope( [&] { timeToDie = true; cv_.notify_all(); }, abort ); std::unique_lock lock( mutex_ ); const auto nowInMs = GetTimestampInMs(); auto nextAvailableTime = timeStamps_.front() + limitPeriod_; if ( incomingRequests_.empty() && nowInMs >= nextAvailableTime ) { timeStamps_.emplace_back( nowInMs ); return; } auto waitTime = nextAvailableTime - nowInMs; const auto requestIdx = curRequestIdx_++; incomingRequests_.emplace_back( requestIdx ); const auto curIt = std::prev( incomingRequests_.end() ); const qwr::final_action autoEraseRequest( [&] { incomingRequests_.erase( curIt ); } ); while ( true ) { if ( shouldLogWebApiDebug_ ) { FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (debug):\n" << fmt::format( "throttling for {} milliseconds", waitTime.count() ); } bool hasEvent = cv_.wait_for( lock, waitTime, [&] { return timeToDie || ( incomingRequests_.front() == requestIdx && GetTimestampInMs() >= nextAvailableTime ); } ); if ( hasEvent ) { if ( !timeToDie ) { timeStamps_.emplace_back( GetTimestampInMs() ); } return; } nextAvailableTime = GetTimestampInMs() + limitPeriod_ / limitCount_; waitTime = limitPeriod_ / limitCount_; } } } // namespace sptf ================================================ FILE: foo_spotify/utils/rps_limiter.h ================================================ #pragma once #include #include #include #include #include namespace sptf { class RpsLimiter { public: RpsLimiter( size_t limit, std::chrono::seconds limitPeriod = std::chrono::seconds( 1 ) ); ~RpsLimiter() = default; void WaitForRequestAvailability( abort_callback& abort ); private: const bool shouldLogWebApiDebug_; const std::chrono::seconds limitPeriod_; const size_t limitCount_; std::mutex mutex_; std::condition_variable cv_; std::vector timeStampsContainer_; nonstd::ring_span< std::chrono::milliseconds, nonstd::null_popper> timeStamps_; size_t curRequestIdx_ = 0; // TODO: replace list with queue std::list incomingRequests_; }; } // namespace sptf ================================================ FILE: foo_spotify/utils/secure_vector.h ================================================ #pragma once #include namespace sptf { template class SecureAllocator { public: static_assert( !std::is_const::value, "The C++ Standard forbids containers of const elements " "because allocator is ill-formed." ); using value_type = T; using propagate_on_container_move_assignment = std::true_type; using is_always_equal = std::true_type; constexpr SecureAllocator() noexcept { } constexpr SecureAllocator( const SecureAllocator& ) noexcept = default; template constexpr SecureAllocator( const SecureAllocator& ) noexcept { } void deallocate( T* const ptr, const size_t count ) { SecureZeroMemory( reinterpret_cast( ptr ), count * sizeof( T ) ); baseAllocator_.deallocate( ptr, count ); } [[nodiscard]] T* allocate( const size_t count ) { return baseAllocator_.allocate( count ); } private: std::allocator baseAllocator_; }; template [[nodiscard]] inline bool operator==( const SecureAllocator&, const SecureAllocator& ) noexcept { return true; } template [[nodiscard]] inline bool operator!=( const SecureAllocator&, const SecureAllocator& ) noexcept { return false; } template using SecureVector = std::vector>; } // namespace sptf ================================================ FILE: foo_spotify/utils/sleeper.cpp ================================================ #include #include "sleeper.h" #include #include #include #include namespace sptf { bool SleepFor( const std::chrono::milliseconds& duration, abort_callback& abort ) { std::mutex mtx; std::condition_variable cv; bool timeToDie = false; auto& am = SpotifyInstance::Get().GetAbortManager(); auto abortableScope = am.GetAbortableScope( [&] { { std::lock_guard lock( mtx ); timeToDie = true; } cv.notify_all(); }, abort ); std::unique_lock lock( mtx ); return !cv.wait_for( lock, duration, [&] { return timeToDie; } ); } } // namespace sptf ================================================ FILE: foo_spotify/utils/sleeper.h ================================================ #pragma once #include namespace sptf { bool SleepFor( const std::chrono::milliseconds& duration, abort_callback& abort ); } // namespace sptf ================================================ FILE: libspotify/ChangeLog ================================================ Version 12 Added Android support. Added functionality for controlling scrobbling. Added function sp_session_user_name, suitable for using in conjunction with the credentials_blob_updated callback. Added connectionstate_updated callback. Added support for low- and high-res images. Fixed a bug where libspotify would crash on reusing credentials when password had been changed. Fixed a bug where sp_search_total_playlists would always return 0. Fixed apparently random crash on multi-core ARM devices. Various bug fixes. Version 11 The end_of_track() callback in session has changed and is now invoked on the main thread. Depending on how your application use this callback some change might be necessary. Artist browse when used in SP_ARTISTBROWSE_NO_TRACKS or SP_ARTISTBROWSE_NO_ALBUMS also includes a list of top tracks for the Artist. The SP_ARTISTBROWSE_FULL mode has been deprecated and will be removed in a future release. Artist browse have been improved with tophit tracks. See sp_artistbrowse_tophit_track() and sp_artistbrowse_num_tophit_tracks() New method sp_session_flush_caches(). This will make libspotify write all data that is meant to be stored on disk to the disk immediately. Libspotify does this periodically by itself and also on logout. So under normal conditions this should never need to be used. Removed support for Mac OS X - PowerPC. New callback, credentials_blob_updated() has been added. This callback will be invoked when a login blob has been received by the client. You can use this blob to relogin a user (via sp_session_login()) much like the remember_me argument. The difference between remember_me and the blob callback is that the blob callback allows multiple users to be relogged in by having the application store a blob for each user. Useful if your application supports multiple users sp_track_get_playable() will return the track that would actually be played if a track is "linked". Linked tracks are redirect from an unplayable track to a track on a different album (which is available in the currently logged in user's region) Normally, your application does not need to worry about this but the function is here for completeness. The radio has been removed. The reason for this is that the radio features have been redesigned in the Spotify backend. A new feature have been added that allows a user to see the number of new tracks added on a subscribed playlist. sp_playlistcontainer_get_unseen_tracks() returns number of new tracks (and optionally the tracks themselves) sp_playlistcontainer_clear_unseen_tracks() can be used to clear the unseen track counter. This feature is not available in the desktop client yet. Version 10 sp_track_is_available() has been renamed to sp_track_get_availability() This new function returns more detailed information about why a track is not streamable. Add error code SP_ERROR_NETWORK_DISABLED Add error code SP_ERROR_INVALID_DEVICE_ID Add sp_track_offline_get_status() to return offline status of a track. Add sp_track_is_placeholder() that indicates if a track is a placeholder track. Placeholder tracks are used to store items in playlists that are not tracks but rather than changing the entire API to support arbitrary items the sp_track object will act as a proxy object. The placeholder track object only contains the URI to the real object. IE. after having determined that a track is a placeholder track the API user should use sp_link_create_from_track() to get an URI to the real object and continue to resolve the object from there. Modify sp_artistbrowse_create() to take a type argument. Prior to this release sp_artistbrowse_create() queried Spotify's backend servers for all tracks related to an artist and doing an artist browse. This consumes a lot of memory on the host and for various embedded platforms this could result in a crash. This new version of sp_artistbrowse_create() offers a way to only query for the albums when using 'SP_ARTISTBROWSE_NO_TRACKS' (this would typically result in 95% less memory used) or just data about the artist itself 'SP_ARTISTBROWSE_NO_ALBUMS'. As most user interfaces list albums when browsing an artist 'SP_ARTISTBROWSE_NO_TRACKS' should almost always be used. Add support for controlling volume normalization. Note that volume normalization is not enabled on some targets. In particular that includes all arm-targets and all target without fast floating point (where libspotify uses fixed point vorbis decoder instead) A couple of functions ending with _backend_request_duration() has been added. These functions can be used when analyzing performance in an application to see how much time is spent on waiting for a request to be served by the Spotify backend A new session callback offline_error has been added that is called if there is some general problems with offline synchronization of tracks. libspotifys social integration requires a HTTPS client. This needs to be supplied by the application. For an example of how to do this (using libcurl) see the spshell example (spshell.c) Version 9 SPOTIFY_API_VERSION is bumped to 9. Package version numbers have changed. The distributed package (and the is so versioning where applicable) is now versioned like this: libspotify-X.Y.Z X - Major version. Always same as SPOTIFY_API_VERSION. libraries with different major versions are not ABI compatible Y - Minor version. Is 0 during development. 1 for the first public release, etc Z - Patch version. Incremented for each change of the codebase libspotify can now remember user credentials. This should be used to automatically login the user. The application MUST NEVER store the user's password. - A fourth parameter has been added to sp_session_login() that will tell libspotify to remember the login credentials in an encrypted form stored in the settings_location as specified when creating the session. - To relogin a user, the application should invoke sp_session_relogin() instead of sp_session_login(). If no credentials are stored, SP_ERROR_NO_CREDENTIALS will be returned - To clear stored credentials, use sp_session_forget_me() - To get username of currently stored credentials use sp_session_remembered_user() Add sp_session_offline_time_left() that returns time left until the user is required to reconnect to Spotify in order to revalidate the offline keys. The logged in state has been updated to include SP_CONNECTION_STATE_OFFLINE, which indicates that the user is logged in to the Spotify service in offline mode. The SP_ERROR_RESOURCE_NOT_LOADED, which was only used by sp_session_player_load(), has been replaced with SP_ERROR_IS_LOADING which means the same thing. The documentation has been updated to fix a few minor errors. callback connection_error() will no longer be invoked if the error parameter has value SP_ERROR_OK, as it isn't an error. sp_track_set_starred and sp_playlist_add_tracks got their list arguments const property changed from (const**) to (*const*). sp_link_create_from_artist_portrait() renamed to sp_link_create_from_artistbrowse_portrait() which is more appropriate. Introduced sp_artist_portrait() and sp_link_create_from_artist_portrait() to get one artist portrait from an artist object. This should be used to get artist portraits when doing search and top list browse queries. Introduced sp_build_id() - This could be useful to display somewhere deep down in the user interface in case you (or Spotify) would like to know the exact version running. Version 0.0.8 SPOTIFY_API_VERSION is bumped to 8. Added support for offline mode. Users of libspotify can now request playlists to be offline synchronized just as Spotify's desktop and mobile applications. For example of how to do this, please see cmd_playlist_offline() in playlist.c in the spshell example. Added sp_playlistcontainer_is_loaded() which can be used to check if a playlistcontainer is loaded. This value will be true after the container_loaded() callback is invoked. Added support for spotify:image: linktype. If your application needs an abstract way to refer to coverarts, artist portraits, etc this is what you should use. Add sp_session_user_country() that returns the currently logged in users registered country. This is the country that should be used to get toplists localized for the user. bugfix: LocalTrack URI handling (sp_link) was broken. This has now been fixed bufix: libspotify was unable to link on 10.4 and 10.5. This has now been fixed Version 0.0.7 SPOTIFY_API_VERSION is bumped to 7. IMPORTANT BEHAVIOR CHANGE: Prior to 0.0.7 libspotify would remove all notification callbacks the user had registered on a playlist if the playlist was removed from the root container. This caused a problem such that if a playlist was still visible in a user interface and got removed from the root container no further updates to the list would be visible. There is really no good reason to have such behavior and therefore libspotify no longer remove the registered notifications. So to be compatible with libspotify 0.0.7 and maintain the previous behavior an application MUST unregister the callbacks it has registered when the sp_playlistcontainer_callbacks::playlist_removed notification is invoked. All the examples shipped with libspotify already do this so if you have followed them you should be OK. feature: libspotify is now available for ARMv5, ARMv6 and ARMv7 on Linux/glibc bugfix: In 0.0.6 sp_playlistcontainer_playlist_folder_name() returned a pointer to free'd memory as the folder names are internally generated on the fly. Now the caller will have to provide a buffer to store the string instead. bugfix: If sp_playlistcontainer_add_callbacks() was called before login had succeed libspotify would crash. This was a regression in 0.0.6 bugfix: sp_session_publishedcontainer_for_user() did not initialize the playlist subsystem internally in the lib resulting in a crash. This could be circumvented by invoking another playlist related API call before calling sp_session_publishedcontainer_for_user(). bugfix: sp_session_publishedcontainer_for_user() has been removed in favor of sp_session_publishedcontainer_for_user_create() which creates a new object that the caller own and thus needs to release using sp_playlistcontainer_release(). In 0.0.6 the sp_session_publishedcontainer_for_user() function did not claim a reference for the container internally and thus the container would eventually be flushed by the playlist garbage collector resulting in a crash. All functions that return errors now have documentation that describers the errors they can return. SP_ERROR_NO_CACHE and SP_ERROR_NO_SUCH_USER error codes have been added. Inbox functionality now have support for messages attached to each track. Add support for creating playlist folders. See notes in sp_playlistcontainer_add_folder() for how to delete and rename folders. Make it possible to modify seen status of tracks in playlist. See sp_playlist_track_set_seen() Add support for playlist subscription information. See sp_playlist_update_subscribers() The 'tiny_settings' session configuration flag has been removed in favor of more granular flags. See documentation in sp_session_config for more details. Add support for moving playlists out of RAM and store bulk of the information on disk. See sp_playlist_is_in_ram() documentation for more details. The spshell example did not initialize unused fields in sp_session_config to zero. This is good practice to avoid accidentally enabling features when compiling with a newer version of the lib. Playlist containers can now be properly reference counted. See sp_playlistcontainer_add_ref() and sp_playlistcontainer_release(). Version 0.0.6 SPOTIFY_API_VERSION is bumped to 6. feature: Playlist folders. The folders are implemented as specialized playlists. Two specialized types of playlists can be used to push/pop depth in the a playlist container view. See sp_playlist_type and sp_playlistcontainer_playlist_type(). feature: Auto linking of tracks, see sp_track_is_autolinked(). feature: Add support for extracting playlist description and playlist images. feature: Make it possible to query if a track is a local track. See sp_track_is_local(). feature: sp_session_release() is now available and thus, sp_session_init() has been renamed to sp_session_create() feature: Improved audio delivery handling. An application may choose to implement a callback in the session struct that allows libspotify to query the application audio buffer statistics. The application should return number of samples currently in its buffer and also if it has experienced any audio dropouts (stutters) since the last query. The query callback (get_audio_buffer_stats) will be invoked ca 10 times per second. If an application implements the get_audio_buffer_stats() it may also choose to implement the start_playback() and stop_playback() callbacks. The start_playback() callback will be invoked when there is enough samples in the buffer and the session player is in play mode,see sp_session_player_play() The stop_playback() callback will be invoked when playback is paused and when a track ends. If an application reports that it has experienced stutter it may put itself in paused state and wait for start_playback() to be invoked. This will happen once libspotify is satisfied with the buffer fullness. No stop_playback() will be invoked in this case. feature: Prefetching of tracks has been added. See sp_session_player_prefetch(). Note that prefetch only works if the caching is enabled. feature: A new session configuration variable 'tiny_settings' is available that, when set, tries to minimize the data store in the settings dir as much as possible without affecting the user experience too much. Set this if free disk space is precious. Version 0.0.5 SPOTIFY_API_VERSION is bumped to 5. feature: The cache size can now be explicitly configured. See sp_session_set_cache_size(). feature: Support for getting create user and time for playlist entries. See sp_playlist_track_creator() sp_playlist_track_create_time() and the track_created_changed callback. feature: Support for loading currently logged in user's Inbox. See sp_session_inbox(), sp_playlist_track_seen() and the track_seen_changed playlist callback. feature: Three new link types SP_LINKTYPE_PROFILE, SP_LINKTYPE_STARRED and SP_LINKTYPE_LOCALTRACK feature: Support for browsing other users toplists. See sp_toplistbrowse_create(). feature: Support for browsing published lists. See sp_session_publishedcontainer_for_user() and sp_session_publishedcontainer_for_user_release(), also you can get the user object for a playlistcontainer using sp_playlistcontainer_owner. feature: Support creating local tracks. See sp_localtrack_create(). ABI change: sp_track_is_available() & sp_track_is_starred() now need sp_session to be passed as an argument. Version 0.0.4 SPOTIFY_API_VERSION is bumped to 4. feature: libspotify is now available for Win32 and OS/X platforms. feature: Add support for starred tracks. See sp_track_is_starred(), sp_track_set_starred() and sp_session_starred_create(). feature: Add support for radio. See sp_radio_search_create(). feature: Add support for loading playlist without adding them to the users's playlist container. See sp_playlist_create(). feature: Add support for 320kb/s streaming. See sp_session_preferred_bitrate(). feature: Added new session callback streaming_error() that is invoked if the library is unable to play the requested track. feature: Add new session callback userinfo_updated() that is invoked when one or more sp_user entries have been updated. ABI change: Due to internal changes, the sp_playlist_add_tracks() needs sp_session to be passed as an argument. API change: The include file has been renamed from spotify/api.h to libspotify/api.h for coherency reasons. Version 0.0.3 SPOTIFY_API_VERSION is bumped to 3. feature: libspotify is now available for 64-bit x86 Linux platforms feature: sp_artistbrowse_num_albums() and sp_artistbrowse_album() have been added so users of the API can list all albums returned in an artist browse. bugfix: SP_ERROR_OTHER_PERMAMENT renamed to SP_ERROR_OTHER_PERMANENT feature: The sp_image API has changed. libspotify now exposes the source image data via the API. See sp_image_data() and sp_image_format() feature: Playlistcontainer have a new callback container_loaded() that is invoked after the container has been initially synchronized from the server. feature: Added support for toplists. See the sp_toplistbrowse API functions. feature: Add sp_link_as_track_and_offset() for getting play start offset encoded in URIs. Version 0.0.2 SPOTIFY_API_VERSION is bumped to 2. feature: sp_search_create() has been extended with offset and limit for album and artist results. bugfix: libspotify contained a bug that caused it to hang if a playlist was removed when libspotify was running. feature: sp_track_is_available() has been added to allow the user to query if a track can be played or not due to regional restrictions. feature: sp_album_is_available() has been added to allow the user to query if a album is available or not due to regional restrictions. feature: playlist_metadata_updated() per-playlist callback has been added, allowing the user to refresh metadata for tracks in a playlist. feature: sp_album_type() has been added to allow the user to find out the album type. See the sp_albumtype enum for the available types. feature: An end_of_track() session callback has been added. Prior to this release the music_delivery() callback would be invoked with num_frames set to 0 both to indicate audio discontinuity (as a result of a seek from the main thread) and also to indicate end-of-track. To ease application development, end_of_track() is now invoked at the end of a track and music_delivery() is only invoked with num_frames set to 0 as a result of a seek. Version 0.0.1 Initial release ================================================ FILE: libspotify/LICENSE ================================================ For the current terms and conditions, please read: http://developer.spotify.com/en/libspotify/terms-of-use/ ================================================ FILE: libspotify/README ================================================ libspotify 12.1.51 ---------------------------------------- Copyright 2006-2012 Spotify Ltd 1. INTRODUCTION This is the libspotify C API package. With it, you can write applications that utilize the Spotify music streaming service. 2. PACKAGE STRUCTURE The package is structured as follows: libspotify\ README.txt This README file LICENSE.txt License governing the library, and associated files ChangeLog.txt Contains description of new and changed functionality in libspotify licenses.xhtml License governing third party libraries included in libspotify. include\libspotify\api.h Header file for the library lib\ libspotify.dll libspotify DLL file libspotify.lib Import library doc\ html\ Documentation in HTML format This information is also available online. images\ Graphics for use with applications using libspotify. See section 6 below. examples\ Root directory for the example source code 3. INSTALLATION You have probably unpacked the distribution zip archive already. To run the compiled examples you need to include the directory where libspotify.dll resides in your system PATH environment variable. For more details see this MSDN article about Windows DLL search paths: http://msdn.microsoft.com/en-us/library/7d83bc18.aspx 4. APPLICATION KEYS The example programs require a file called appkey.c which is not present in the package. You will need to request an application key at the developer website at http://developer.spotify.com/ before being able to run them. 5. LOGOTYPES AND TRADEMARK The graphics found in the images/ subdirectory are available for use in applications using libspotify. The terms and conditions for the use of these are available on http://developer.spotify.com/. A text file is included in images/, containing the text to be used in non-graphical applications. 6. LICENSE You should have received a separate LICENSE file together with this README. ================================================ FILE: libspotify/docs/html/annotated.html ================================================ libspotify: Data Structures

Data Structures


Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/api_8h.html ================================================ libspotify: api.h File Reference

api.h File Reference

#include <stddef.h>
#include <stdint.h>

Data Structures

struct  sp_audioformat
struct  sp_audio_buffer_stats
struct  sp_subscribers
struct  sp_offline_sync_status
struct  sp_session_callbacks
struct  sp_session_config
struct  sp_playlist_callbacks
struct  sp_playlistcontainer_callbacks

Defines

#define SPOTIFY_API_VERSION   12
#define SP_TOPLIST_REGION(a, b)   ((a) << 8 | (b))

Typedefs

typedef struct sp_session sp_session
 Representation of a session.
typedef struct sp_track sp_track
 A track handle.
typedef struct sp_album sp_album
 An album handle.
typedef struct sp_artist sp_artist
 An artist handle.
typedef struct sp_artistbrowse sp_artistbrowse
 A handle to an artist browse result.
typedef struct sp_albumbrowse sp_albumbrowse
 A handle to an album browse result.
typedef struct sp_toplistbrowse sp_toplistbrowse
 A handle to a toplist browse result.
typedef struct sp_search sp_search
 A handle to a search result.
typedef struct sp_link sp_link
 A handle to the libspotify internal representation of a URI.
typedef struct sp_image sp_image
 A handle to an image.
typedef struct sp_user sp_user
 A handle to a user.
typedef struct sp_playlist sp_playlist
 A playlist handle.
typedef struct sp_playlistcontainer sp_playlistcontainer
 A playlist container (playlist containing other playlists) handle.
typedef struct sp_inbox sp_inbox
 Add to inbox request handle.
typedef enum sp_error sp_error
typedef enum sp_connectionstate sp_connectionstate
typedef enum sp_sampletype sp_sampletype
typedef struct sp_audioformat sp_audioformat
typedef enum sp_bitrate sp_bitrate
typedef enum sp_playlist_type sp_playlist_type
typedef enum sp_search_type sp_search_type
typedef enum
sp_playlist_offline_status 
sp_playlist_offline_status
typedef enum sp_availability sp_track_availability
typedef enum
sp_track_offline_status 
sp_track_offline_status
typedef enum sp_image_size sp_image_size
typedef struct
sp_audio_buffer_stats 
sp_audio_buffer_stats
typedef struct sp_subscribers sp_subscribers
typedef enum sp_connection_type sp_connection_type
typedef enum sp_connection_rules sp_connection_rules
typedef enum sp_artistbrowse_type sp_artistbrowse_type
typedef struct
sp_offline_sync_status 
sp_offline_sync_status
typedef struct sp_session_callbacks sp_session_callbacks
typedef struct sp_session_config sp_session_config
typedef void albumbrowse_complete_cb (sp_albumbrowse *result, void *userdata)
typedef void artistbrowse_complete_cb (sp_artistbrowse *result, void *userdata)
typedef void image_loaded_cb (sp_image *image, void *userdata)
typedef void search_complete_cb (sp_search *result, void *userdata)
typedef struct
sp_playlist_callbacks 
sp_playlist_callbacks
typedef struct
sp_playlistcontainer_callbacks 
sp_playlistcontainer_callbacks
typedef enum sp_relation_type sp_relation_type
typedef void toplistbrowse_complete_cb (sp_toplistbrowse *result, void *userdata)
typedef void inboxpost_complete_cb (sp_inbox *result, void *userdata)

Enumerations

enum  sp_error {
  SP_ERROR_OK = 0,
  SP_ERROR_BAD_API_VERSION = 1,
  SP_ERROR_API_INITIALIZATION_FAILED = 2,
  SP_ERROR_TRACK_NOT_PLAYABLE = 3,
  SP_ERROR_BAD_APPLICATION_KEY = 5,
  SP_ERROR_BAD_USERNAME_OR_PASSWORD = 6,
  SP_ERROR_USER_BANNED = 7,
  SP_ERROR_UNABLE_TO_CONTACT_SERVER = 8,
  SP_ERROR_CLIENT_TOO_OLD = 9,
  SP_ERROR_OTHER_PERMANENT = 10,
  SP_ERROR_BAD_USER_AGENT = 11,
  SP_ERROR_MISSING_CALLBACK = 12,
  SP_ERROR_INVALID_INDATA = 13,
  SP_ERROR_INDEX_OUT_OF_RANGE = 14,
  SP_ERROR_USER_NEEDS_PREMIUM = 15,
  SP_ERROR_OTHER_TRANSIENT = 16,
  SP_ERROR_IS_LOADING = 17,
  SP_ERROR_NO_STREAM_AVAILABLE = 18,
  SP_ERROR_PERMISSION_DENIED = 19,
  SP_ERROR_INBOX_IS_FULL = 20,
  SP_ERROR_NO_CACHE = 21,
  SP_ERROR_NO_SUCH_USER = 22,
  SP_ERROR_NO_CREDENTIALS = 23,
  SP_ERROR_NETWORK_DISABLED = 24,
  SP_ERROR_INVALID_DEVICE_ID = 25,
  SP_ERROR_CANT_OPEN_TRACE_FILE = 26,
  SP_ERROR_APPLICATION_BANNED = 27,
  SP_ERROR_OFFLINE_TOO_MANY_TRACKS = 31,
  SP_ERROR_OFFLINE_DISK_CACHE = 32,
  SP_ERROR_OFFLINE_EXPIRED = 33,
  SP_ERROR_OFFLINE_NOT_ALLOWED = 34,
  SP_ERROR_OFFLINE_LICENSE_LOST = 35,
  SP_ERROR_OFFLINE_LICENSE_ERROR = 36,
  SP_ERROR_LASTFM_AUTH_ERROR = 39,
  SP_ERROR_INVALID_ARGUMENT = 40,
  SP_ERROR_SYSTEM_FAILURE = 41
}
enum  sp_connectionstate {
  SP_CONNECTION_STATE_LOGGED_OUT = 0,
  SP_CONNECTION_STATE_LOGGED_IN = 1,
  SP_CONNECTION_STATE_DISCONNECTED = 2,
  SP_CONNECTION_STATE_UNDEFINED = 3,
  SP_CONNECTION_STATE_OFFLINE = 4
}
enum  sp_sampletype { SP_SAMPLETYPE_INT16_NATIVE_ENDIAN = 0 }
enum  sp_bitrate {
  SP_BITRATE_160k = 0,
  SP_BITRATE_320k = 1,
  SP_BITRATE_96k = 2
}
enum  sp_playlist_type {
  SP_PLAYLIST_TYPE_PLAYLIST = 0,
  SP_PLAYLIST_TYPE_START_FOLDER = 1,
  SP_PLAYLIST_TYPE_END_FOLDER = 2,
  SP_PLAYLIST_TYPE_PLACEHOLDER = 3
}
enum  sp_search_type
enum  sp_playlist_offline_status {
  SP_PLAYLIST_OFFLINE_STATUS_NO = 0,
  SP_PLAYLIST_OFFLINE_STATUS_YES = 1,
  SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING = 2,
  SP_PLAYLIST_OFFLINE_STATUS_WAITING = 3
}
enum  sp_availability {
  SP_TRACK_AVAILABILITY_UNAVAILABLE = 0,
  SP_TRACK_AVAILABILITY_AVAILABLE = 1,
  SP_TRACK_AVAILABILITY_NOT_STREAMABLE = 2,
  SP_TRACK_AVAILABILITY_BANNED_BY_ARTIST = 3
}
enum  sp_track_offline_status {
  SP_TRACK_OFFLINE_NO = 0,
  SP_TRACK_OFFLINE_WAITING = 1,
  SP_TRACK_OFFLINE_DOWNLOADING = 2,
  SP_TRACK_OFFLINE_DONE = 3,
  SP_TRACK_OFFLINE_ERROR = 4,
  SP_TRACK_OFFLINE_DONE_EXPIRED = 5,
  SP_TRACK_OFFLINE_LIMIT_EXCEEDED = 6,
  SP_TRACK_OFFLINE_DONE_RESYNC = 7
}
enum  sp_image_size {
  SP_IMAGE_SIZE_NORMAL = 0,
  SP_IMAGE_SIZE_SMALL = 1,
  SP_IMAGE_SIZE_LARGE = 2
}
enum  sp_connection_type {
  SP_CONNECTION_TYPE_UNKNOWN = 0,
  SP_CONNECTION_TYPE_NONE = 1,
  SP_CONNECTION_TYPE_MOBILE = 2,
  SP_CONNECTION_TYPE_MOBILE_ROAMING = 3,
  SP_CONNECTION_TYPE_WIFI = 4,
  SP_CONNECTION_TYPE_WIRED = 5
}
enum  sp_connection_rules {
  SP_CONNECTION_RULE_NETWORK = 0x1,
  SP_CONNECTION_RULE_NETWORK_IF_ROAMING = 0x2,
  SP_CONNECTION_RULE_ALLOW_SYNC_OVER_MOBILE = 0x4,
  SP_CONNECTION_RULE_ALLOW_SYNC_OVER_WIFI = 0x8
}
enum  sp_artistbrowse_type {
  SP_ARTISTBROWSE_FULL,
  SP_ARTISTBROWSE_NO_TRACKS,
  SP_ARTISTBROWSE_NO_ALBUMS
}
enum  sp_linktype {
  SP_LINKTYPE_INVALID = 0,
  SP_LINKTYPE_TRACK = 1,
  SP_LINKTYPE_ALBUM = 2,
  SP_LINKTYPE_ARTIST = 3,
  SP_LINKTYPE_SEARCH = 4,
  SP_LINKTYPE_PLAYLIST = 5,
  SP_LINKTYPE_PROFILE = 6,
  SP_LINKTYPE_STARRED = 7,
  SP_LINKTYPE_LOCALTRACK = 8,
  SP_LINKTYPE_IMAGE = 9
}
enum  sp_albumtype {
  SP_ALBUMTYPE_ALBUM = 0,
  SP_ALBUMTYPE_SINGLE = 1,
  SP_ALBUMTYPE_COMPILATION = 2,
  SP_ALBUMTYPE_UNKNOWN = 3
}
enum  sp_imageformat {
  SP_IMAGE_FORMAT_UNKNOWN = -1,
  SP_IMAGE_FORMAT_JPEG = 0
}
enum  sp_relation_type {
  SP_RELATION_TYPE_UNKNOWN = 0,
  SP_RELATION_TYPE_NONE = 1,
  SP_RELATION_TYPE_UNIDIRECTIONAL = 2,
  SP_RELATION_TYPE_BIDIRECTIONAL = 3
}
enum  sp_toplisttype {
  SP_TOPLIST_TYPE_ARTISTS = 0,
  SP_TOPLIST_TYPE_ALBUMS = 1,
  SP_TOPLIST_TYPE_TRACKS = 2
}
enum  sp_toplistregion {
  SP_TOPLIST_REGION_EVERYWHERE = 0,
  SP_TOPLIST_REGION_USER = 1
}

Functions

const char * sp_error_message (sp_error error)
sp_error sp_session_create (const sp_session_config *config, sp_session **sess)
sp_error sp_session_release (sp_session *sess)
sp_error sp_session_login (sp_session *session, const char *username, const char *password, bool remember_me, const char *blob)
sp_error sp_session_relogin (sp_session *session)
int sp_session_remembered_user (sp_session *session, char *buffer, size_t buffer_size)
const char * sp_session_user_name (sp_session *session)
sp_error sp_session_forget_me (sp_session *session)
sp_usersp_session_user (sp_session *session)
sp_error sp_session_logout (sp_session *session)
sp_error sp_session_flush_caches (sp_session *session)
sp_connectionstate sp_session_connectionstate (sp_session *session)
void * sp_session_userdata (sp_session *session)
sp_error sp_session_set_cache_size (sp_session *session, size_t size)
sp_error sp_session_process_events (sp_session *session, int *next_timeout)
sp_error sp_session_player_load (sp_session *session, sp_track *track)
sp_error sp_session_player_seek (sp_session *session, int offset)
sp_error sp_session_player_play (sp_session *session, bool play)
sp_error sp_session_player_unload (sp_session *session)
sp_error sp_session_player_prefetch (sp_session *session, sp_track *track)
sp_playlistcontainersp_session_playlistcontainer (sp_session *session)
sp_playlistsp_session_inbox_create (sp_session *session)
sp_playlistsp_session_starred_create (sp_session *session)
sp_playlistsp_session_starred_for_user_create (sp_session *session, const char *canonical_username)
sp_playlistcontainersp_session_publishedcontainer_for_user_create (sp_session *session, const char *canonical_username)
sp_error sp_session_preferred_bitrate (sp_session *session, sp_bitrate bitrate)
sp_error sp_session_preferred_offline_bitrate (sp_session *session, sp_bitrate bitrate, bool allow_resync)
bool sp_session_get_volume_normalization (sp_session *session)
sp_error sp_session_set_volume_normalization (sp_session *session, bool on)
sp_error sp_session_set_private_session (sp_session *session, bool enabled)
bool sp_session_is_private_session (sp_session *session)
sp_error sp_session_set_scrobbling (sp_session *session, sp_social_provider provider, sp_scrobbling_state state)
sp_error sp_session_is_scrobbling (sp_session *session, sp_social_provider provider, sp_scrobbling_state *state)
sp_error sp_session_is_scrobbling_possible (sp_session *session, sp_social_provider provider, bool *out)
sp_error sp_session_set_social_credentials (sp_session *session, sp_social_provider provider, const char *username, const char *password)
sp_error sp_session_set_connection_type (sp_session *session, sp_connection_type type)
sp_error sp_session_set_connection_rules (sp_session *session, sp_connection_rules rules)
int sp_offline_tracks_to_sync (sp_session *session)
int sp_offline_num_playlists (sp_session *session)
bool sp_offline_sync_get_status (sp_session *session, sp_offline_sync_status *status)
int sp_offline_time_left (sp_session *session)
int sp_session_user_country (sp_session *session)
sp_linksp_link_create_from_string (const char *link)
sp_linksp_link_create_from_track (sp_track *track, int offset)
sp_linksp_link_create_from_album (sp_album *album)
sp_linksp_link_create_from_album_cover (sp_album *album, sp_image_size size)
sp_linksp_link_create_from_artist (sp_artist *artist)
sp_linksp_link_create_from_artist_portrait (sp_artist *artist, sp_image_size size)
sp_linksp_link_create_from_artistbrowse_portrait (sp_artistbrowse *arb, int index)
sp_linksp_link_create_from_search (sp_search *search)
sp_linksp_link_create_from_playlist (sp_playlist *playlist)
sp_linksp_link_create_from_user (sp_user *user)
sp_linksp_link_create_from_image (sp_image *image)
int sp_link_as_string (sp_link *link, char *buffer, int buffer_size)
sp_linktype sp_link_type (sp_link *link)
sp_tracksp_link_as_track (sp_link *link)
sp_tracksp_link_as_track_and_offset (sp_link *link, int *offset)
sp_albumsp_link_as_album (sp_link *link)
sp_artistsp_link_as_artist (sp_link *link)
sp_usersp_link_as_user (sp_link *link)
sp_error sp_link_add_ref (sp_link *link)
sp_error sp_link_release (sp_link *link)
bool sp_track_is_loaded (sp_track *track)
sp_error sp_track_error (sp_track *track)
sp_track_offline_status sp_track_offline_get_status (sp_track *track)
sp_track_availability sp_track_get_availability (sp_session *session, sp_track *track)
bool sp_track_is_local (sp_session *session, sp_track *track)
bool sp_track_is_autolinked (sp_session *session, sp_track *track)
sp_tracksp_track_get_playable (sp_session *session, sp_track *track)
bool sp_track_is_placeholder (sp_track *track)
bool sp_track_is_starred (sp_session *session, sp_track *track)
sp_error sp_track_set_starred (sp_session *session, sp_track *const *tracks, int num_tracks, bool star)
int sp_track_num_artists (sp_track *track)
sp_artistsp_track_artist (sp_track *track, int index)
sp_albumsp_track_album (sp_track *track)
const char * sp_track_name (sp_track *track)
int sp_track_duration (sp_track *track)
int sp_track_popularity (sp_track *track)
int sp_track_disc (sp_track *track)
int sp_track_index (sp_track *track)
sp_tracksp_localtrack_create (const char *artist, const char *title, const char *album, int length)
sp_error sp_track_add_ref (sp_track *track)
sp_error sp_track_release (sp_track *track)
bool sp_album_is_loaded (sp_album *album)
bool sp_album_is_available (sp_album *album)
sp_artistsp_album_artist (sp_album *album)
const byte * sp_album_cover (sp_album *album, sp_image_size size)
const char * sp_album_name (sp_album *album)
int sp_album_year (sp_album *album)
sp_albumtype sp_album_type (sp_album *album)
sp_error sp_album_add_ref (sp_album *album)
sp_error sp_album_release (sp_album *album)
const char * sp_artist_name (sp_artist *artist)
bool sp_artist_is_loaded (sp_artist *artist)
const byte * sp_artist_portrait (sp_artist *artist, sp_image_size size)
sp_error sp_artist_add_ref (sp_artist *artist)
sp_error sp_artist_release (sp_artist *artist)
sp_albumbrowsesp_albumbrowse_create (sp_session *session, sp_album *album, albumbrowse_complete_cb *callback, void *userdata)
bool sp_albumbrowse_is_loaded (sp_albumbrowse *alb)
sp_error sp_albumbrowse_error (sp_albumbrowse *alb)
sp_albumsp_albumbrowse_album (sp_albumbrowse *alb)
sp_artistsp_albumbrowse_artist (sp_albumbrowse *alb)
int sp_albumbrowse_num_copyrights (sp_albumbrowse *alb)
const char * sp_albumbrowse_copyright (sp_albumbrowse *alb, int index)
int sp_albumbrowse_num_tracks (sp_albumbrowse *alb)
sp_tracksp_albumbrowse_track (sp_albumbrowse *alb, int index)
const char * sp_albumbrowse_review (sp_albumbrowse *alb)
int sp_albumbrowse_backend_request_duration (sp_albumbrowse *alb)
sp_error sp_albumbrowse_add_ref (sp_albumbrowse *alb)
sp_error sp_albumbrowse_release (sp_albumbrowse *alb)
sp_artistbrowsesp_artistbrowse_create (sp_session *session, sp_artist *artist, sp_artistbrowse_type type, artistbrowse_complete_cb *callback, void *userdata)
bool sp_artistbrowse_is_loaded (sp_artistbrowse *arb)
sp_error sp_artistbrowse_error (sp_artistbrowse *arb)
sp_artistsp_artistbrowse_artist (sp_artistbrowse *arb)
int sp_artistbrowse_num_portraits (sp_artistbrowse *arb)
const byte * sp_artistbrowse_portrait (sp_artistbrowse *arb, int index)
int sp_artistbrowse_num_tracks (sp_artistbrowse *arb)
sp_tracksp_artistbrowse_track (sp_artistbrowse *arb, int index)
int sp_artistbrowse_num_tophit_tracks (sp_artistbrowse *arb)
sp_tracksp_artistbrowse_tophit_track (sp_artistbrowse *arb, int index)
int sp_artistbrowse_num_albums (sp_artistbrowse *arb)
sp_albumsp_artistbrowse_album (sp_artistbrowse *arb, int index)
int sp_artistbrowse_num_similar_artists (sp_artistbrowse *arb)
sp_artistsp_artistbrowse_similar_artist (sp_artistbrowse *arb, int index)
const char * sp_artistbrowse_biography (sp_artistbrowse *arb)
int sp_artistbrowse_backend_request_duration (sp_artistbrowse *arb)
sp_error sp_artistbrowse_add_ref (sp_artistbrowse *arb)
sp_error sp_artistbrowse_release (sp_artistbrowse *arb)
sp_imagesp_image_create (sp_session *session, const byte image_id[20])
sp_imagesp_image_create_from_link (sp_session *session, sp_link *l)
sp_error sp_image_add_load_callback (sp_image *image, image_loaded_cb *callback, void *userdata)
sp_error sp_image_remove_load_callback (sp_image *image, image_loaded_cb *callback, void *userdata)
bool sp_image_is_loaded (sp_image *image)
sp_error sp_image_error (sp_image *image)
sp_imageformat sp_image_format (sp_image *image)
const void * sp_image_data (sp_image *image, size_t *data_size)
const byte * sp_image_image_id (sp_image *image)
sp_error sp_image_add_ref (sp_image *image)
sp_error sp_image_release (sp_image *image)
sp_searchsp_search_create (sp_session *session, const char *query, int track_offset, int track_count, int album_offset, int album_count, int artist_offset, int artist_count, int playlist_offset, int playlist_count, sp_search_type search_type, search_complete_cb *callback, void *userdata)
bool sp_search_is_loaded (sp_search *search)
sp_error sp_search_error (sp_search *search)
int sp_search_num_tracks (sp_search *search)
sp_tracksp_search_track (sp_search *search, int index)
int sp_search_num_albums (sp_search *search)
sp_albumsp_search_album (sp_search *search, int index)
int sp_search_num_playlists (sp_search *search)
sp_playlistsp_search_playlist (sp_search *search, int index)
const char * sp_search_playlist_name (sp_search *search, int index)
const char * sp_search_playlist_uri (sp_search *search, int index)
const char * sp_search_playlist_image_uri (sp_search *search, int index)
int sp_search_num_artists (sp_search *search)
sp_artistsp_search_artist (sp_search *search, int index)
const char * sp_search_query (sp_search *search)
const char * sp_search_did_you_mean (sp_search *search)
int sp_search_total_tracks (sp_search *search)
int sp_search_total_albums (sp_search *search)
int sp_search_total_artists (sp_search *search)
int sp_search_total_playlists (sp_search *search)
sp_error sp_search_add_ref (sp_search *search)
sp_error sp_search_release (sp_search *search)
bool sp_playlist_is_loaded (sp_playlist *playlist)
sp_error sp_playlist_add_callbacks (sp_playlist *playlist, sp_playlist_callbacks *callbacks, void *userdata)
sp_error sp_playlist_remove_callbacks (sp_playlist *playlist, sp_playlist_callbacks *callbacks, void *userdata)
int sp_playlist_num_tracks (sp_playlist *playlist)
sp_tracksp_playlist_track (sp_playlist *playlist, int index)
int sp_playlist_track_create_time (sp_playlist *playlist, int index)
sp_usersp_playlist_track_creator (sp_playlist *playlist, int index)
bool sp_playlist_track_seen (sp_playlist *playlist, int index)
sp_error sp_playlist_track_set_seen (sp_playlist *playlist, int index, bool seen)
const char * sp_playlist_track_message (sp_playlist *playlist, int index)
const char * sp_playlist_name (sp_playlist *playlist)
sp_error sp_playlist_rename (sp_playlist *playlist, const char *new_name)
sp_usersp_playlist_owner (sp_playlist *playlist)
bool sp_playlist_is_collaborative (sp_playlist *playlist)
sp_error sp_playlist_set_collaborative (sp_playlist *playlist, bool collaborative)
sp_error sp_playlist_set_autolink_tracks (sp_playlist *playlist, bool link)
const char * sp_playlist_get_description (sp_playlist *playlist)
bool sp_playlist_get_image (sp_playlist *playlist, byte image[20])
bool sp_playlist_has_pending_changes (sp_playlist *playlist)
sp_error sp_playlist_add_tracks (sp_playlist *playlist, sp_track *const *tracks, int num_tracks, int position, sp_session *session)
sp_error sp_playlist_remove_tracks (sp_playlist *playlist, const int *tracks, int num_tracks)
sp_error sp_playlist_reorder_tracks (sp_playlist *playlist, const int *tracks, int num_tracks, int new_position)
unsigned int sp_playlist_num_subscribers (sp_playlist *playlist)
sp_subscriberssp_playlist_subscribers (sp_playlist *playlist)
sp_error sp_playlist_subscribers_free (sp_subscribers *subscribers)
sp_error sp_playlist_update_subscribers (sp_session *session, sp_playlist *playlist)
bool sp_playlist_is_in_ram (sp_session *session, sp_playlist *playlist)
sp_error sp_playlist_set_in_ram (sp_session *session, sp_playlist *playlist, bool in_ram)
sp_playlistsp_playlist_create (sp_session *session, sp_link *link)
sp_error sp_playlist_set_offline_mode (sp_session *session, sp_playlist *playlist, bool offline)
sp_playlist_offline_status sp_playlist_get_offline_status (sp_session *session, sp_playlist *playlist)
int sp_playlist_get_offline_download_completed (sp_session *session, sp_playlist *playlist)
sp_error sp_playlist_add_ref (sp_playlist *playlist)
sp_error sp_playlist_release (sp_playlist *playlist)
sp_error sp_playlistcontainer_add_callbacks (sp_playlistcontainer *pc, sp_playlistcontainer_callbacks *callbacks, void *userdata)
sp_error sp_playlistcontainer_remove_callbacks (sp_playlistcontainer *pc, sp_playlistcontainer_callbacks *callbacks, void *userdata)
int sp_playlistcontainer_num_playlists (sp_playlistcontainer *pc)
bool sp_playlistcontainer_is_loaded (sp_playlistcontainer *pc)
sp_playlistsp_playlistcontainer_playlist (sp_playlistcontainer *pc, int index)
sp_playlist_type sp_playlistcontainer_playlist_type (sp_playlistcontainer *pc, int index)
sp_error sp_playlistcontainer_playlist_folder_name (sp_playlistcontainer *pc, int index, char *buffer, int buffer_size)
sp_uint64 sp_playlistcontainer_playlist_folder_id (sp_playlistcontainer *pc, int index)
sp_playlistsp_playlistcontainer_add_new_playlist (sp_playlistcontainer *pc, const char *name)
sp_playlistsp_playlistcontainer_add_playlist (sp_playlistcontainer *pc, sp_link *link)
sp_error sp_playlistcontainer_remove_playlist (sp_playlistcontainer *pc, int index)
sp_error sp_playlistcontainer_move_playlist (sp_playlistcontainer *pc, int index, int new_position, bool dry_run)
sp_error sp_playlistcontainer_add_folder (sp_playlistcontainer *pc, int index, const char *name)
sp_usersp_playlistcontainer_owner (sp_playlistcontainer *pc)
sp_error sp_playlistcontainer_add_ref (sp_playlistcontainer *pc)
sp_error sp_playlistcontainer_release (sp_playlistcontainer *pc)
int sp_playlistcontainer_get_unseen_tracks (sp_playlistcontainer *pc, sp_playlist *playlist, sp_track **tracks, int num_tracks)
int sp_playlistcontainer_clear_unseen_tracks (sp_playlistcontainer *pc, sp_playlist *playlist)
const char * sp_user_canonical_name (sp_user *user)
const char * sp_user_display_name (sp_user *user)
bool sp_user_is_loaded (sp_user *user)
sp_error sp_user_add_ref (sp_user *user)
sp_error sp_user_release (sp_user *user)
sp_toplistbrowsesp_toplistbrowse_create (sp_session *session, sp_toplisttype type, sp_toplistregion region, const char *username, toplistbrowse_complete_cb *callback, void *userdata)
bool sp_toplistbrowse_is_loaded (sp_toplistbrowse *tlb)
sp_error sp_toplistbrowse_error (sp_toplistbrowse *tlb)
sp_error sp_toplistbrowse_add_ref (sp_toplistbrowse *tlb)
sp_error sp_toplistbrowse_release (sp_toplistbrowse *tlb)
int sp_toplistbrowse_num_artists (sp_toplistbrowse *tlb)
sp_artistsp_toplistbrowse_artist (sp_toplistbrowse *tlb, int index)
int sp_toplistbrowse_num_albums (sp_toplistbrowse *tlb)
sp_albumsp_toplistbrowse_album (sp_toplistbrowse *tlb, int index)
int sp_toplistbrowse_num_tracks (sp_toplistbrowse *tlb)
sp_tracksp_toplistbrowse_track (sp_toplistbrowse *tlb, int index)
int sp_toplistbrowse_backend_request_duration (sp_toplistbrowse *tlb)
sp_inboxsp_inbox_post_tracks (sp_session *session, const char *user, sp_track *const *tracks, int num_tracks, const char *message, inboxpost_complete_cb *callback, void *userdata)
sp_error sp_inbox_error (sp_inbox *inbox)
sp_error sp_inbox_add_ref (sp_inbox *inbox)
sp_error sp_inbox_release (sp_inbox *inbox)
const char * sp_build_id (void)

Detailed Description

Public API for libspotify

Note:
All input strings are expected to be in UTF-8
All output strings are in UTF-8.
All usernames are valid XMPP nodeprep identifiers: http://tools.ietf.org/html/rfc3920#appendix-A If you need to store user data, we strongly advise you to use the canonical form of the username.

Function Documentation

const char* sp_build_id ( void   ) 

Return the libspotify build ID

This might be useful to have available for display somewhere in your user interface.


Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/browse_8c-example.html ================================================ libspotify: browse.c

browse.c

The browse.c example shows how you can use the album, artist, and browse functions. The example also include some rudimentary playlist browsing. It is part of the spshell program

#include "spshell.h"
#include "cmd.h"

static sp_track *track_browse;
static sp_playlist *playlist_browse;
static sp_playlist_callbacks pl_callbacks;

void print_track(sp_track *track)
{
    int duration = sp_track_duration(track);
    char url[256];
    sp_link *l;
    int i;

#if WIN32
    printf(" %s ", sp_track_is_starred(g_session,track) ? "*" : " ");
#else
    printf(" %s ", sp_track_is_starred(g_session,track) ? "★" : "☆");
#endif
    printf("Track %s [%d:%02d] has %d artist(s), %d%% popularity",
           sp_track_name(track),
           duration / 60000,
           (duration / 1000) % 60,
           sp_track_num_artists(track),
           sp_track_popularity(track));
    
    if(sp_track_disc(track)) 
        printf(", %d on disc %d",
               sp_track_index(track),
               sp_track_disc(track));
    printf("\n");

    for (i = 0; i < sp_track_num_artists(track); i++) {
        sp_artist *art = sp_track_artist(track, i);
        printf("\tArtist %d: %s\n", i + 1, sp_artist_name(art));
    }
    l = sp_link_create_from_track(track, 0);
    sp_link_as_string(l, url, sizeof(url));
    printf("\t\t%s\n", url);
    sp_link_release(l);
}

static void print_albumbrowse(sp_albumbrowse *browse)
{
    int i;

    printf("Album browse of \"%s\" (%d)\n", 
           sp_album_name(sp_albumbrowse_album(browse)), 
           sp_album_year(sp_albumbrowse_album(browse)));

    for (i = 0; i < sp_albumbrowse_num_copyrights(browse); ++i)
        printf("  Copyright: %s\n", sp_albumbrowse_copyright(browse, i));

    printf("  Tracks: %d\n", sp_albumbrowse_num_tracks(browse));
    printf("  Review: %.60s...\n", sp_albumbrowse_review(browse));
    puts("");

    for (i = 0; i < sp_albumbrowse_num_tracks(browse); ++i)
        print_track(sp_albumbrowse_track(browse, i));

    puts("");
}

static void print_artistbrowse(sp_artistbrowse *browse)
{
    int i;

    printf("Artist browse of \"%s\"\n", sp_artist_name(sp_artistbrowse_artist(browse)));

    for (i = 0; i < sp_artistbrowse_num_similar_artists(browse); ++i)
        printf("  Similar artist: %s\n", sp_artist_name(sp_artistbrowse_similar_artist(browse, i)));

    printf("  Portraits: %d\n", sp_artistbrowse_num_portraits(browse));
    printf("  Tracks   : %d\n", sp_artistbrowse_num_tracks(browse));
    printf("  Biography: %.60s...\n", sp_artistbrowse_biography(browse));
    puts("");

    for (i = 0; i < sp_artistbrowse_num_tracks(browse); ++i)
        print_track(sp_artistbrowse_track(browse, i));

    puts("");
}



static void SP_CALLCONV browse_album_callback(sp_albumbrowse *browse, void *userdata)
{
    if (sp_albumbrowse_error(browse) == SP_ERROR_OK)
        print_albumbrowse(browse);
    else
        fprintf(stderr, "Failed to browse album: %s\n",
                sp_error_message(sp_albumbrowse_error(browse)));

    sp_albumbrowse_release(browse);
    cmd_done();
}


static void SP_CALLCONV browse_artist_callback(sp_artistbrowse *browse, void *userdata)
{
    if (sp_artistbrowse_error(browse) == SP_ERROR_OK)
        print_artistbrowse(browse);
    else
        fprintf(stderr, "Failed to browse artist: %s\n",
                sp_error_message(sp_artistbrowse_error(browse)));

    sp_artistbrowse_release(browse);
    cmd_done();
}



static void track_browse_try(void)
{
    switch (sp_track_error(track_browse)) {
    case SP_ERROR_OK:
        print_track(track_browse);
        break;

    case SP_ERROR_IS_LOADING:
        return; // Still pending

    default:
        fprintf(stderr, "Unable to resolve track: %s\n", sp_error_message(sp_track_error(track_browse)));
        break;
    }
    
    metadata_updated_fn = NULL;
    cmd_done();
    sp_track_release(track_browse);
}



static void playlist_browse_try(void)
{
    int i, tracks;

    metadata_updated_fn = playlist_browse_try;
    if(!sp_playlist_is_loaded(playlist_browse)) {
        printf("\tPlaylist not loaded\n");
        return;
    }

    tracks = sp_playlist_num_tracks(playlist_browse);
    for(i = 0; i < tracks; i++) {
        sp_track *t = sp_playlist_track(playlist_browse, i);
        if (!sp_track_is_loaded(t))
            return;
    }

    printf("\tPlaylist and metadata loaded\n");

    for(i = 0; i < tracks; i++) {
        sp_track *t = sp_playlist_track(playlist_browse, i);
        
        printf(" %5d: ", i + 1);
        print_track(t);
    }
    sp_playlist_remove_callbacks(playlist_browse, &pl_callbacks, NULL);

    sp_playlist_release(playlist_browse);
    playlist_browse = NULL;
    metadata_updated_fn = NULL;
    cmd_done();
}

static void SP_CALLCONV pl_tracks_added(sp_playlist *pl, sp_track * const * tracks,
                int num_tracks, int position, void *userdata)
{
    printf("\t%d tracks added\n", num_tracks);
}

static void SP_CALLCONV pl_tracks_removed(sp_playlist *pl, const int *tracks,
                  int num_tracks, void *userdata)
{
    printf("\t%d tracks removed\n", num_tracks);
}

static void SP_CALLCONV pl_tracks_moved(sp_playlist *pl, const int *tracks,
                int num_tracks, int new_position, void *userdata)
{
    printf("\t%d tracks moved\n", num_tracks);
}

static void SP_CALLCONV pl_renamed(sp_playlist *pl, void *userdata)
{
    printf("\tList name: %s\n",  sp_playlist_name(pl));
}

static void SP_CALLCONV pl_state_change(sp_playlist *pl, void *userdata)
{
    playlist_browse_try();
}

static sp_playlist_callbacks pl_callbacks = {
    pl_tracks_added,
    pl_tracks_removed,
    pl_tracks_moved,
    pl_renamed,
    pl_state_change,
};


void browse_playlist(sp_playlist *pl)
{
    playlist_browse = pl;
    sp_playlist_add_callbacks(playlist_browse, &pl_callbacks, NULL);
    playlist_browse_try();
}

static void browse_usage(void)
{
    fprintf(stderr, "Usage: browse <spotify-uri>\n");
}


int cmd_browse(int argc, char **argv)
{
    sp_link *link;

    if (argc != 2) {
        browse_usage();
        return -1;
    }

    
    link = sp_link_create_from_string(argv[1]);
    
    if (!link) {
        fprintf(stderr, "Not a spotify link\n");
        return -1;
    }

    switch(sp_link_type(link)) {
    default:
        fprintf(stderr, "Can not handle link");
        sp_link_release(link);
        return -1;

    case SP_LINKTYPE_ALBUM:
        sp_albumbrowse_create(g_session, sp_link_as_album(link), browse_album_callback, NULL);
        break;

    case SP_LINKTYPE_ARTIST:
        sp_artistbrowse_create(g_session, sp_link_as_artist(link), SP_ARTISTBROWSE_FULL, browse_artist_callback, NULL);
        break;

    case SP_LINKTYPE_LOCALTRACK:
    case SP_LINKTYPE_TRACK:
        track_browse = sp_link_as_track(link);
        metadata_updated_fn = track_browse_try;
        sp_track_add_ref(track_browse);
        track_browse_try();
        break;

    case SP_LINKTYPE_PLAYLIST:
        browse_playlist(sp_playlist_create(g_session, link));
        break;
    }

    sp_link_release(link);
    return 0;
}

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/classes.html ================================================ libspotify: Alphabetical List

Data Structure Index


Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/doxygen.css ================================================ /* Copyright (c) 2009 Spotify Ltd */ body { margin:0; background:#fff; color:#444; padding:0; } body,h1,h2,h3,h4,h5,p,td,li { font-family:'Helvetica neue',sans-serif; color:#000; } body,td,p,li,div { line-height:1.5em; font-size:14px; color:#444; } /* @group Heading Levels */ h1,h2,h3,h4,h5 { color:#222; } h1 > a, h2 > a, h3 > a, h4 > a { color:#222; } h1 { font-size: 180%; } h2 { font-size: 150%; margin-top:2em; } td h2 { margin-top:0.5em; font-weight:normal; } h3 { font-size: 120%; } /* @end */ caption { font-weight: bold; } div.qindex, div.navtab { background-color: #e8eef2; border: 1px solid #84b0c7; text-align: center; margin: 2px; padding: 2px; } div.qindex, div.navpath { width:100%; line-height:140%; } div.navtab { margin-right: 15px; } div.contents { margin:0 3em; } /* @group Link Styling */ div.contents a { text-decoration:none; } div.contents a:link { color:#266a99; } div.contents a:hover { text-decoration:underline; } div.contents a:visited { color:#606a99; } div.contents a:link:hover { color:#006be4; background:#e2f4ff; } div.contents a:visited:hover { color:#af00cf; background:#f4e6ff; } a.qindex { font-weight: bold; } a.qindexHL { font-weight: bold; background-color: #6666cc; color: #ffffff; border: 1px double #9295C2; } .contents a.qindexHL:visited { color: #ffffff; } a.el { font-weight: bold; } a.elRef {} a.code {} a.codeRef {} /* @end */ dl.el { margin-left: -1cm; } .fragment { font-family: monospace, fixed; } pre.fragment { border: 3px solid #fff; background-color: #f0f3d9; color:#38330b; padding: 1em; margin: 1em 0; } div.ah { background-color: black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px } div.groupHeader { margin-left: 16px; margin-top: 12px; margin-bottom: 6px; font-weight: bold; } div.groupText { margin-left: 16px; font-style: italic; } div.navigation { margin-bottom:2em; } td.indexkey { background-color: #e8eef2; font-weight: bold; border: 1px solid #CCCCCC; margin: 2px 0px 2px 0; padding: 2px 10px; } td.indexvalue { background-color: #e8eef2; border: 1px solid #CCCCCC; padding: 2px 10px; margin: 2px 0px; } tr.memlist { background-color: #f0f0f0; } p.formulaDsp { text-align: center; } img.formulaDsp {} img.formulaInl { vertical-align: middle; } /* @group Code Colorization */ span.keyword { color: #2550b0; } span.keywordtype { color: #604020; } span.keywordflow { color: #9d5303; } span.comment { color: #8f9277; font-style:italic; } span.preprocessor { color: #806020; } span.stringliteral { color: #1d783b; } span.charliteral { color: #008080; } span.vhdldigit { color: #ff00ff; } span.vhdlchar { color: #000000; } span.vhdlkeyword { color: #700070; } span.vhdllogic { color: #ff0000; } /* @end */ .search { color:#003399; font-weight:bold; } form.search { margin-bottom:0px; margin-top:0px; } input.search { font-size:75%; color:#000080; font-weight:normal; background-color:#e8eef2; } td.tiny { font-size: 75%; } .dirtab { padding: 4px; border-collapse: collapse; border: 1px solid #84b0c7; } th.dirtab { background: #e8eef2; font-weight: bold; } hr { height:0; border:none; border-top:1px solid #ccc; } /* @group Member Descriptions */ .mdescLeft, .mdescRight, .memItemLeft, .memItemRight, .memTemplItemLeft, .memTemplItemRight, .memTemplParams { background-color: #eee; border: none; margin: 4px; padding: 0.2em; font-family:monospace; } .mdescLeft, .memItemLeft, .memTemplItemLeft { padding-left:1em; } .mdescRight, .memItemRight, .memTemplItemRight { padding-right:1em; } .mdescLeft, .mdescRight { padding: 0px 8px 4px 8px; color: #555; } .memItemLeft, .memItemRight, .memTemplParams { border-top: 1px solid #ccc; } .memTemplParams { color: #606060; } /* @end */ /* @group Member Details */ /* Styles for detailed member documentation */ .memtemplate { font-size: 80%; color: #606060; font-weight: normal; margin-left: 3px; } .memnav { background-color: #e8eef2; border: 1px solid #84b0c7; text-align: center; margin: 2px; margin-right: 15px; padding: 2px; } .memitem { padding: 0; } .memname { white-space:nowrap; font-weight:bold; } .memproto, .memdoc {} .memproto { padding: 0.3em 0.8em; background-color: #cae2c9; font-weight: bold; -webkit-border-top-left-radius: 8px; -webkit-border-top-right-radius: 8px; -moz-border-radius-topleft: 8px; -moz-border-radius-topright: 8px; } .memproto td { font-family:monospace; } .memproto td, .memdoc td { color:#103a0f; } .memdoc { padding: 0.3em 0.8em; background-color: #eaf5e9; border-top-width: 0; -webkit-border-bottom-left-radius: 8px; -webkit-border-bottom-right-radius: 8px; -moz-border-radius-bottomleft: 8px; -moz-border-radius-bottomright: 8px; } .paramkey { text-align:right; } .paramtype { white-space:nowrap; } .paramname { color:#602020; white-space:nowrap; } .paramname em { font-style: normal; } /* @end */ /* @group Directory (tree) */ /* for the tree view */ .ftvtree { font-family:sans-serif; margin:0.5em; } /* these are for tree view when used as main index */ .directory { font-size: 9pt; font-weight: bold; } .directory h3 { margin: 0px; margin-top: 1em; font-size: 11pt; } /* The following two styles can be used to replace the root node title with an image of your choice. Simply uncomment the next two styles, specify the name of your image and be sure to set 'height' to the proper pixel height of your image. */ /* .directory h3.swap { height: 61px; background-repeat: no-repeat; background-image: url("yourimage.gif"); } .directory h3.swap span { display: none; } */ .directory > h3 { margin-top: 0; } .directory p { margin: 0px; white-space: nowrap; } .directory div { display: none; margin: 0px; } .directory img { vertical-align:-30%; } /* these are for tree view when not used as main index */ .directory-alt { font-size: 100%; font-weight: bold; } .directory-alt h3 { margin: 0px; margin-top: 1em; font-size: 11pt; } .directory-alt > h3 { margin-top: 0; } .directory-alt p { margin: 0px; white-space: nowrap; } .directory-alt div { display: none; margin: 0px; } .directory-alt img { vertical-align: -30%; } /* @end */ /* Footer */ body > hr { margin-top:4em; } body > address { padding:0 3em; font-style: normal; color: #999; margin-bottom:.5em; text-align:left !important; } body > address br { display:none; } /* print */ @media print { div.contents { padding-left:10%; padding-right:6%; } } ================================================ FILE: libspotify/docs/html/examples.html ================================================ libspotify: Examples

Examples

Here is a list of all examples:

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/files.html ================================================ libspotify: File Index

File List

Here is a list of all documented files with brief descriptions:
api.h

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/functions.html ================================================ libspotify: Data Fields
Here is a list of all documented struct and union fields with links to the struct/union documentation for each field:

- a -

- c -

- d -

- e -

- g -

- i -

- l -

- m -

- n -

- o -

- p -

- q -

- s -

- t -

- u -

- w -


Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/functions_vars.html ================================================ libspotify: Data Fields - Variables
 

- a -

- c -

- d -

- e -

- g -

- i -

- l -

- m -

- n -

- o -

- p -

- q -

- s -

- t -

- u -

- w -


Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/globals.html ================================================ libspotify: Data Fields
Here is a list of all documented functions, variables, defines, enums, and typedefs with links to the documentation:

- a -

  • albumbrowse_complete_cb : api.h
  • artistbrowse_complete_cb : api.h

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/globals_0x69.html ================================================ libspotify: Data Fields
Here is a list of all documented functions, variables, defines, enums, and typedefs with links to the documentation:

- i -

  • image_loaded_cb : api.h
  • inboxpost_complete_cb : api.h

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/globals_0x73.html ================================================ libspotify: Data Fields
Here is a list of all documented functions, variables, defines, enums, and typedefs with links to the documentation:

- s -

  • search_complete_cb : api.h
  • sp_album : api.h
  • sp_album_add_ref() : api.h
  • sp_album_artist() : api.h
  • sp_album_cover() : api.h
  • sp_album_is_available() : api.h
  • sp_album_is_loaded() : api.h
  • sp_album_name() : api.h
  • sp_album_release() : api.h
  • sp_album_type() : api.h
  • sp_album_year() : api.h
  • sp_albumbrowse : api.h
  • sp_albumbrowse_add_ref() : api.h
  • sp_albumbrowse_album() : api.h
  • sp_albumbrowse_artist() : api.h
  • sp_albumbrowse_backend_request_duration() : api.h
  • sp_albumbrowse_copyright() : api.h
  • sp_albumbrowse_create() : api.h
  • sp_albumbrowse_error() : api.h
  • sp_albumbrowse_is_loaded() : api.h
  • sp_albumbrowse_num_copyrights() : api.h
  • sp_albumbrowse_num_tracks() : api.h
  • sp_albumbrowse_release() : api.h
  • sp_albumbrowse_review() : api.h
  • sp_albumbrowse_track() : api.h
  • sp_albumtype : api.h
  • SP_ALBUMTYPE_ALBUM : api.h
  • SP_ALBUMTYPE_COMPILATION : api.h
  • SP_ALBUMTYPE_SINGLE : api.h
  • SP_ALBUMTYPE_UNKNOWN : api.h
  • sp_artist : api.h
  • sp_artist_add_ref() : api.h
  • sp_artist_is_loaded() : api.h
  • sp_artist_name() : api.h
  • sp_artist_portrait() : api.h
  • sp_artist_release() : api.h
  • sp_artistbrowse : api.h
  • sp_artistbrowse_add_ref() : api.h
  • sp_artistbrowse_album() : api.h
  • sp_artistbrowse_artist() : api.h
  • sp_artistbrowse_backend_request_duration() : api.h
  • sp_artistbrowse_biography() : api.h
  • sp_artistbrowse_create() : api.h
  • sp_artistbrowse_error() : api.h
  • SP_ARTISTBROWSE_FULL : api.h
  • sp_artistbrowse_is_loaded() : api.h
  • SP_ARTISTBROWSE_NO_ALBUMS : api.h
  • SP_ARTISTBROWSE_NO_TRACKS : api.h
  • sp_artistbrowse_num_albums() : api.h
  • sp_artistbrowse_num_portraits() : api.h
  • sp_artistbrowse_num_similar_artists() : api.h
  • sp_artistbrowse_num_tophit_tracks() : api.h
  • sp_artistbrowse_num_tracks() : api.h
  • sp_artistbrowse_portrait() : api.h
  • sp_artistbrowse_release() : api.h
  • sp_artistbrowse_similar_artist() : api.h
  • sp_artistbrowse_tophit_track() : api.h
  • sp_artistbrowse_track() : api.h
  • sp_artistbrowse_type : api.h
  • sp_audio_buffer_stats : api.h
  • sp_audioformat : api.h
  • sp_availability : api.h
  • sp_bitrate : api.h
  • SP_BITRATE_160k : api.h
  • SP_BITRATE_320k : api.h
  • SP_BITRATE_96k : api.h
  • sp_build_id() : api.h
  • SP_CONNECTION_RULE_ALLOW_SYNC_OVER_MOBILE : api.h
  • SP_CONNECTION_RULE_ALLOW_SYNC_OVER_WIFI : api.h
  • SP_CONNECTION_RULE_NETWORK : api.h
  • SP_CONNECTION_RULE_NETWORK_IF_ROAMING : api.h
  • sp_connection_rules : api.h
  • SP_CONNECTION_STATE_DISCONNECTED : api.h
  • SP_CONNECTION_STATE_LOGGED_IN : api.h
  • SP_CONNECTION_STATE_LOGGED_OUT : api.h
  • SP_CONNECTION_STATE_OFFLINE : api.h
  • SP_CONNECTION_STATE_UNDEFINED : api.h
  • sp_connection_type : api.h
  • SP_CONNECTION_TYPE_MOBILE : api.h
  • SP_CONNECTION_TYPE_MOBILE_ROAMING : api.h
  • SP_CONNECTION_TYPE_NONE : api.h
  • SP_CONNECTION_TYPE_UNKNOWN : api.h
  • SP_CONNECTION_TYPE_WIFI : api.h
  • SP_CONNECTION_TYPE_WIRED : api.h
  • sp_connectionstate : api.h
  • sp_error : api.h
  • SP_ERROR_API_INITIALIZATION_FAILED : api.h
  • SP_ERROR_APPLICATION_BANNED : api.h
  • SP_ERROR_BAD_API_VERSION : api.h
  • SP_ERROR_BAD_APPLICATION_KEY : api.h
  • SP_ERROR_BAD_USER_AGENT : api.h
  • SP_ERROR_BAD_USERNAME_OR_PASSWORD : api.h
  • SP_ERROR_CANT_OPEN_TRACE_FILE : api.h
  • SP_ERROR_CLIENT_TOO_OLD : api.h
  • SP_ERROR_INBOX_IS_FULL : api.h
  • SP_ERROR_INDEX_OUT_OF_RANGE : api.h
  • SP_ERROR_INVALID_ARGUMENT : api.h
  • SP_ERROR_INVALID_DEVICE_ID : api.h
  • SP_ERROR_INVALID_INDATA : api.h
  • SP_ERROR_IS_LOADING : api.h
  • SP_ERROR_LASTFM_AUTH_ERROR : api.h
  • sp_error_message() : api.h
  • SP_ERROR_MISSING_CALLBACK : api.h
  • SP_ERROR_NETWORK_DISABLED : api.h
  • SP_ERROR_NO_CACHE : api.h
  • SP_ERROR_NO_CREDENTIALS : api.h
  • SP_ERROR_NO_STREAM_AVAILABLE : api.h
  • SP_ERROR_NO_SUCH_USER : api.h
  • SP_ERROR_OFFLINE_DISK_CACHE : api.h
  • SP_ERROR_OFFLINE_EXPIRED : api.h
  • SP_ERROR_OFFLINE_LICENSE_ERROR : api.h
  • SP_ERROR_OFFLINE_LICENSE_LOST : api.h
  • SP_ERROR_OFFLINE_NOT_ALLOWED : api.h
  • SP_ERROR_OFFLINE_TOO_MANY_TRACKS : api.h
  • SP_ERROR_OK : api.h
  • SP_ERROR_OTHER_PERMANENT : api.h
  • SP_ERROR_OTHER_TRANSIENT : api.h
  • SP_ERROR_PERMISSION_DENIED : api.h
  • SP_ERROR_SYSTEM_FAILURE : api.h
  • SP_ERROR_TRACK_NOT_PLAYABLE : api.h
  • SP_ERROR_UNABLE_TO_CONTACT_SERVER : api.h
  • SP_ERROR_USER_BANNED : api.h
  • SP_ERROR_USER_NEEDS_PREMIUM : api.h
  • sp_image : api.h
  • sp_image_add_load_callback() : api.h
  • sp_image_add_ref() : api.h
  • sp_image_create() : api.h
  • sp_image_create_from_link() : api.h
  • sp_image_data() : api.h
  • sp_image_error() : api.h
  • sp_image_format() : api.h
  • SP_IMAGE_FORMAT_JPEG : api.h
  • SP_IMAGE_FORMAT_UNKNOWN : api.h
  • sp_image_image_id() : api.h
  • sp_image_is_loaded() : api.h
  • sp_image_release() : api.h
  • sp_image_remove_load_callback() : api.h
  • sp_image_size : api.h
  • SP_IMAGE_SIZE_LARGE : api.h
  • SP_IMAGE_SIZE_NORMAL : api.h
  • SP_IMAGE_SIZE_SMALL : api.h
  • sp_imageformat : api.h
  • sp_inbox : api.h
  • sp_inbox_add_ref() : api.h
  • sp_inbox_error() : api.h
  • sp_inbox_post_tracks() : api.h
  • sp_inbox_release() : api.h
  • sp_link : api.h
  • sp_link_add_ref() : api.h
  • sp_link_as_album() : api.h
  • sp_link_as_artist() : api.h
  • sp_link_as_string() : api.h
  • sp_link_as_track() : api.h
  • sp_link_as_track_and_offset() : api.h
  • sp_link_as_user() : api.h
  • sp_link_create_from_album() : api.h
  • sp_link_create_from_album_cover() : api.h
  • sp_link_create_from_artist() : api.h
  • sp_link_create_from_artist_portrait() : api.h
  • sp_link_create_from_artistbrowse_portrait() : api.h
  • sp_link_create_from_image() : api.h
  • sp_link_create_from_playlist() : api.h
  • sp_link_create_from_search() : api.h
  • sp_link_create_from_string() : api.h
  • sp_link_create_from_track() : api.h
  • sp_link_create_from_user() : api.h
  • sp_link_release() : api.h
  • sp_link_type() : api.h
  • sp_linktype : api.h
  • SP_LINKTYPE_ALBUM : api.h
  • SP_LINKTYPE_ARTIST : api.h
  • SP_LINKTYPE_IMAGE : api.h
  • SP_LINKTYPE_INVALID : api.h
  • SP_LINKTYPE_LOCALTRACK : api.h
  • SP_LINKTYPE_PLAYLIST : api.h
  • SP_LINKTYPE_PROFILE : api.h
  • SP_LINKTYPE_SEARCH : api.h
  • SP_LINKTYPE_STARRED : api.h
  • SP_LINKTYPE_TRACK : api.h
  • sp_localtrack_create() : api.h
  • sp_offline_num_playlists() : api.h
  • sp_offline_sync_get_status() : api.h
  • sp_offline_sync_status : api.h
  • sp_offline_time_left() : api.h
  • sp_offline_tracks_to_sync() : api.h
  • sp_playlist : api.h
  • sp_playlist_add_callbacks() : api.h
  • sp_playlist_add_ref() : api.h
  • sp_playlist_add_tracks() : api.h
  • sp_playlist_callbacks : api.h
  • sp_playlist_create() : api.h
  • sp_playlist_get_description() : api.h
  • sp_playlist_get_image() : api.h
  • sp_playlist_get_offline_download_completed() : api.h
  • sp_playlist_get_offline_status() : api.h
  • sp_playlist_has_pending_changes() : api.h
  • sp_playlist_is_collaborative() : api.h
  • sp_playlist_is_in_ram() : api.h
  • sp_playlist_is_loaded() : api.h
  • sp_playlist_name() : api.h
  • sp_playlist_num_subscribers() : api.h
  • sp_playlist_num_tracks() : api.h
  • sp_playlist_offline_status : api.h
  • SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING : api.h
  • SP_PLAYLIST_OFFLINE_STATUS_NO : api.h
  • SP_PLAYLIST_OFFLINE_STATUS_WAITING : api.h
  • SP_PLAYLIST_OFFLINE_STATUS_YES : api.h
  • sp_playlist_owner() : api.h
  • sp_playlist_release() : api.h
  • sp_playlist_remove_callbacks() : api.h
  • sp_playlist_remove_tracks() : api.h
  • sp_playlist_rename() : api.h
  • sp_playlist_reorder_tracks() : api.h
  • sp_playlist_set_autolink_tracks() : api.h
  • sp_playlist_set_collaborative() : api.h
  • sp_playlist_set_in_ram() : api.h
  • sp_playlist_set_offline_mode() : api.h
  • sp_playlist_subscribers() : api.h
  • sp_playlist_subscribers_free() : api.h
  • sp_playlist_track() : api.h
  • sp_playlist_track_create_time() : api.h
  • sp_playlist_track_creator() : api.h
  • sp_playlist_track_message() : api.h
  • sp_playlist_track_seen() : api.h
  • sp_playlist_track_set_seen() : api.h
  • sp_playlist_type : api.h
  • SP_PLAYLIST_TYPE_END_FOLDER : api.h
  • SP_PLAYLIST_TYPE_PLACEHOLDER : api.h
  • SP_PLAYLIST_TYPE_PLAYLIST : api.h
  • SP_PLAYLIST_TYPE_START_FOLDER : api.h
  • sp_playlist_update_subscribers() : api.h
  • sp_playlistcontainer : api.h
  • sp_playlistcontainer_add_callbacks() : api.h
  • sp_playlistcontainer_add_folder() : api.h
  • sp_playlistcontainer_add_new_playlist() : api.h
  • sp_playlistcontainer_add_playlist() : api.h
  • sp_playlistcontainer_add_ref() : api.h
  • sp_playlistcontainer_callbacks : api.h
  • sp_playlistcontainer_clear_unseen_tracks() : api.h
  • sp_playlistcontainer_get_unseen_tracks() : api.h
  • sp_playlistcontainer_is_loaded() : api.h
  • sp_playlistcontainer_move_playlist() : api.h
  • sp_playlistcontainer_num_playlists() : api.h
  • sp_playlistcontainer_owner() : api.h
  • sp_playlistcontainer_playlist() : api.h
  • sp_playlistcontainer_playlist_folder_id() : api.h
  • sp_playlistcontainer_playlist_folder_name() : api.h
  • sp_playlistcontainer_playlist_type() : api.h
  • sp_playlistcontainer_release() : api.h
  • sp_playlistcontainer_remove_callbacks() : api.h
  • sp_playlistcontainer_remove_playlist() : api.h
  • sp_relation_type : api.h
  • SP_RELATION_TYPE_BIDIRECTIONAL : api.h
  • SP_RELATION_TYPE_NONE : api.h
  • SP_RELATION_TYPE_UNIDIRECTIONAL : api.h
  • SP_RELATION_TYPE_UNKNOWN : api.h
  • sp_sampletype : api.h
  • SP_SAMPLETYPE_INT16_NATIVE_ENDIAN : api.h
  • sp_search : api.h
  • sp_search_add_ref() : api.h
  • sp_search_album() : api.h
  • sp_search_artist() : api.h
  • sp_search_create() : api.h
  • sp_search_did_you_mean() : api.h
  • sp_search_error() : api.h
  • sp_search_is_loaded() : api.h
  • sp_search_num_albums() : api.h
  • sp_search_num_artists() : api.h
  • sp_search_num_playlists() : api.h
  • sp_search_num_tracks() : api.h
  • sp_search_playlist() : api.h
  • sp_search_playlist_image_uri() : api.h
  • sp_search_playlist_name() : api.h
  • sp_search_playlist_uri() : api.h
  • sp_search_query() : api.h
  • sp_search_release() : api.h
  • sp_search_total_albums() : api.h
  • sp_search_total_artists() : api.h
  • sp_search_total_playlists() : api.h
  • sp_search_total_tracks() : api.h
  • sp_search_track() : api.h
  • sp_search_type : api.h
  • sp_session : api.h
  • sp_session_callbacks : api.h
  • sp_session_config : api.h
  • sp_session_connectionstate() : api.h
  • sp_session_create() : api.h
  • sp_session_flush_caches() : api.h
  • sp_session_forget_me() : api.h
  • sp_session_get_volume_normalization() : api.h
  • sp_session_inbox_create() : api.h
  • sp_session_is_private_session() : api.h
  • sp_session_is_scrobbling() : api.h
  • sp_session_is_scrobbling_possible() : api.h
  • sp_session_login() : api.h
  • sp_session_logout() : api.h
  • sp_session_player_load() : api.h
  • sp_session_player_play() : api.h
  • sp_session_player_prefetch() : api.h
  • sp_session_player_seek() : api.h
  • sp_session_player_unload() : api.h
  • sp_session_playlistcontainer() : api.h
  • sp_session_preferred_bitrate() : api.h
  • sp_session_preferred_offline_bitrate() : api.h
  • sp_session_process_events() : api.h
  • sp_session_publishedcontainer_for_user_create() : api.h
  • sp_session_release() : api.h
  • sp_session_relogin() : api.h
  • sp_session_remembered_user() : api.h
  • sp_session_set_cache_size() : api.h
  • sp_session_set_connection_rules() : api.h
  • sp_session_set_connection_type() : api.h
  • sp_session_set_private_session() : api.h
  • sp_session_set_scrobbling() : api.h
  • sp_session_set_social_credentials() : api.h
  • sp_session_set_volume_normalization() : api.h
  • sp_session_starred_create() : api.h
  • sp_session_starred_for_user_create() : api.h
  • sp_session_user() : api.h
  • sp_session_user_country() : api.h
  • sp_session_user_name() : api.h
  • sp_session_userdata() : api.h
  • sp_subscribers : api.h
  • SP_TOPLIST_REGION : api.h
  • SP_TOPLIST_REGION_EVERYWHERE : api.h
  • SP_TOPLIST_REGION_USER : api.h
  • SP_TOPLIST_TYPE_ALBUMS : api.h
  • SP_TOPLIST_TYPE_ARTISTS : api.h
  • SP_TOPLIST_TYPE_TRACKS : api.h
  • sp_toplistbrowse : api.h
  • sp_toplistbrowse_add_ref() : api.h
  • sp_toplistbrowse_album() : api.h
  • sp_toplistbrowse_artist() : api.h
  • sp_toplistbrowse_backend_request_duration() : api.h
  • sp_toplistbrowse_create() : api.h
  • sp_toplistbrowse_error() : api.h
  • sp_toplistbrowse_is_loaded() : api.h
  • sp_toplistbrowse_num_albums() : api.h
  • sp_toplistbrowse_num_artists() : api.h
  • sp_toplistbrowse_num_tracks() : api.h
  • sp_toplistbrowse_release() : api.h
  • sp_toplistbrowse_track() : api.h
  • sp_toplistregion : api.h
  • sp_toplisttype : api.h
  • sp_track : api.h
  • sp_track_add_ref() : api.h
  • sp_track_album() : api.h
  • sp_track_artist() : api.h
  • sp_track_availability : api.h
  • SP_TRACK_AVAILABILITY_AVAILABLE : api.h
  • SP_TRACK_AVAILABILITY_BANNED_BY_ARTIST : api.h
  • SP_TRACK_AVAILABILITY_NOT_STREAMABLE : api.h
  • SP_TRACK_AVAILABILITY_UNAVAILABLE : api.h
  • sp_track_disc() : api.h
  • sp_track_duration() : api.h
  • sp_track_error() : api.h
  • sp_track_get_availability() : api.h
  • sp_track_get_playable() : api.h
  • sp_track_index() : api.h
  • sp_track_is_autolinked() : api.h
  • sp_track_is_loaded() : api.h
  • sp_track_is_local() : api.h
  • sp_track_is_placeholder() : api.h
  • sp_track_is_starred() : api.h
  • sp_track_name() : api.h
  • sp_track_num_artists() : api.h
  • SP_TRACK_OFFLINE_DONE : api.h
  • SP_TRACK_OFFLINE_DONE_EXPIRED : api.h
  • SP_TRACK_OFFLINE_DONE_RESYNC : api.h
  • SP_TRACK_OFFLINE_DOWNLOADING : api.h
  • SP_TRACK_OFFLINE_ERROR : api.h
  • sp_track_offline_get_status() : api.h
  • SP_TRACK_OFFLINE_LIMIT_EXCEEDED : api.h
  • SP_TRACK_OFFLINE_NO : api.h
  • sp_track_offline_status : api.h
  • SP_TRACK_OFFLINE_WAITING : api.h
  • sp_track_popularity() : api.h
  • sp_track_release() : api.h
  • sp_track_set_starred() : api.h
  • sp_user : api.h
  • sp_user_add_ref() : api.h
  • sp_user_canonical_name() : api.h
  • sp_user_display_name() : api.h
  • sp_user_is_loaded() : api.h
  • sp_user_release() : api.h
  • SPOTIFY_API_VERSION : api.h

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/globals_0x74.html ================================================ libspotify: Data Fields
Here is a list of all documented functions, variables, defines, enums, and typedefs with links to the documentation:

- t -

  • toplistbrowse_complete_cb : api.h

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/globals_defs.html ================================================ libspotify: Data Fields
 
  • SP_TOPLIST_REGION : api.h
  • SPOTIFY_API_VERSION : api.h

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/globals_enum.html ================================================ libspotify: Data Fields
 

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/globals_eval.html ================================================ libspotify: Data Fields
 

- s -

  • SP_ALBUMTYPE_ALBUM : api.h
  • SP_ALBUMTYPE_COMPILATION : api.h
  • SP_ALBUMTYPE_SINGLE : api.h
  • SP_ALBUMTYPE_UNKNOWN : api.h
  • SP_ARTISTBROWSE_FULL : api.h
  • SP_ARTISTBROWSE_NO_ALBUMS : api.h
  • SP_ARTISTBROWSE_NO_TRACKS : api.h
  • SP_BITRATE_160k : api.h
  • SP_BITRATE_320k : api.h
  • SP_BITRATE_96k : api.h
  • SP_CONNECTION_RULE_ALLOW_SYNC_OVER_MOBILE : api.h
  • SP_CONNECTION_RULE_ALLOW_SYNC_OVER_WIFI : api.h
  • SP_CONNECTION_RULE_NETWORK : api.h
  • SP_CONNECTION_RULE_NETWORK_IF_ROAMING : api.h
  • SP_CONNECTION_STATE_DISCONNECTED : api.h
  • SP_CONNECTION_STATE_LOGGED_IN : api.h
  • SP_CONNECTION_STATE_LOGGED_OUT : api.h
  • SP_CONNECTION_STATE_OFFLINE : api.h
  • SP_CONNECTION_STATE_UNDEFINED : api.h
  • SP_CONNECTION_TYPE_MOBILE : api.h
  • SP_CONNECTION_TYPE_MOBILE_ROAMING : api.h
  • SP_CONNECTION_TYPE_NONE : api.h
  • SP_CONNECTION_TYPE_UNKNOWN : api.h
  • SP_CONNECTION_TYPE_WIFI : api.h
  • SP_CONNECTION_TYPE_WIRED : api.h
  • SP_ERROR_API_INITIALIZATION_FAILED : api.h
  • SP_ERROR_APPLICATION_BANNED : api.h
  • SP_ERROR_BAD_API_VERSION : api.h
  • SP_ERROR_BAD_APPLICATION_KEY : api.h
  • SP_ERROR_BAD_USER_AGENT : api.h
  • SP_ERROR_BAD_USERNAME_OR_PASSWORD : api.h
  • SP_ERROR_CANT_OPEN_TRACE_FILE : api.h
  • SP_ERROR_CLIENT_TOO_OLD : api.h
  • SP_ERROR_INBOX_IS_FULL : api.h
  • SP_ERROR_INDEX_OUT_OF_RANGE : api.h
  • SP_ERROR_INVALID_ARGUMENT : api.h
  • SP_ERROR_INVALID_DEVICE_ID : api.h
  • SP_ERROR_INVALID_INDATA : api.h
  • SP_ERROR_IS_LOADING : api.h
  • SP_ERROR_LASTFM_AUTH_ERROR : api.h
  • SP_ERROR_MISSING_CALLBACK : api.h
  • SP_ERROR_NETWORK_DISABLED : api.h
  • SP_ERROR_NO_CACHE : api.h
  • SP_ERROR_NO_CREDENTIALS : api.h
  • SP_ERROR_NO_STREAM_AVAILABLE : api.h
  • SP_ERROR_NO_SUCH_USER : api.h
  • SP_ERROR_OFFLINE_DISK_CACHE : api.h
  • SP_ERROR_OFFLINE_EXPIRED : api.h
  • SP_ERROR_OFFLINE_LICENSE_ERROR : api.h
  • SP_ERROR_OFFLINE_LICENSE_LOST : api.h
  • SP_ERROR_OFFLINE_NOT_ALLOWED : api.h
  • SP_ERROR_OFFLINE_TOO_MANY_TRACKS : api.h
  • SP_ERROR_OK : api.h
  • SP_ERROR_OTHER_PERMANENT : api.h
  • SP_ERROR_OTHER_TRANSIENT : api.h
  • SP_ERROR_PERMISSION_DENIED : api.h
  • SP_ERROR_SYSTEM_FAILURE : api.h
  • SP_ERROR_TRACK_NOT_PLAYABLE : api.h
  • SP_ERROR_UNABLE_TO_CONTACT_SERVER : api.h
  • SP_ERROR_USER_BANNED : api.h
  • SP_ERROR_USER_NEEDS_PREMIUM : api.h
  • SP_IMAGE_FORMAT_JPEG : api.h
  • SP_IMAGE_FORMAT_UNKNOWN : api.h
  • SP_IMAGE_SIZE_LARGE : api.h
  • SP_IMAGE_SIZE_NORMAL : api.h
  • SP_IMAGE_SIZE_SMALL : api.h
  • SP_LINKTYPE_ALBUM : api.h
  • SP_LINKTYPE_ARTIST : api.h
  • SP_LINKTYPE_IMAGE : api.h
  • SP_LINKTYPE_INVALID : api.h
  • SP_LINKTYPE_LOCALTRACK : api.h
  • SP_LINKTYPE_PLAYLIST : api.h
  • SP_LINKTYPE_PROFILE : api.h
  • SP_LINKTYPE_SEARCH : api.h
  • SP_LINKTYPE_STARRED : api.h
  • SP_LINKTYPE_TRACK : api.h
  • SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING : api.h
  • SP_PLAYLIST_OFFLINE_STATUS_NO : api.h
  • SP_PLAYLIST_OFFLINE_STATUS_WAITING : api.h
  • SP_PLAYLIST_OFFLINE_STATUS_YES : api.h
  • SP_PLAYLIST_TYPE_END_FOLDER : api.h
  • SP_PLAYLIST_TYPE_PLACEHOLDER : api.h
  • SP_PLAYLIST_TYPE_PLAYLIST : api.h
  • SP_PLAYLIST_TYPE_START_FOLDER : api.h
  • SP_RELATION_TYPE_BIDIRECTIONAL : api.h
  • SP_RELATION_TYPE_NONE : api.h
  • SP_RELATION_TYPE_UNIDIRECTIONAL : api.h
  • SP_RELATION_TYPE_UNKNOWN : api.h
  • SP_SAMPLETYPE_INT16_NATIVE_ENDIAN : api.h
  • SP_TOPLIST_REGION_EVERYWHERE : api.h
  • SP_TOPLIST_REGION_USER : api.h
  • SP_TOPLIST_TYPE_ALBUMS : api.h
  • SP_TOPLIST_TYPE_ARTISTS : api.h
  • SP_TOPLIST_TYPE_TRACKS : api.h
  • SP_TRACK_AVAILABILITY_AVAILABLE : api.h
  • SP_TRACK_AVAILABILITY_BANNED_BY_ARTIST : api.h
  • SP_TRACK_AVAILABILITY_NOT_STREAMABLE : api.h
  • SP_TRACK_AVAILABILITY_UNAVAILABLE : api.h
  • SP_TRACK_OFFLINE_DONE : api.h
  • SP_TRACK_OFFLINE_DONE_EXPIRED : api.h
  • SP_TRACK_OFFLINE_DONE_RESYNC : api.h
  • SP_TRACK_OFFLINE_DOWNLOADING : api.h
  • SP_TRACK_OFFLINE_ERROR : api.h
  • SP_TRACK_OFFLINE_LIMIT_EXCEEDED : api.h
  • SP_TRACK_OFFLINE_NO : api.h
  • SP_TRACK_OFFLINE_WAITING : api.h

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/globals_func.html ================================================ libspotify: Data Fields
 

- s -

  • sp_album_add_ref() : api.h
  • sp_album_artist() : api.h
  • sp_album_cover() : api.h
  • sp_album_is_available() : api.h
  • sp_album_is_loaded() : api.h
  • sp_album_name() : api.h
  • sp_album_release() : api.h
  • sp_album_type() : api.h
  • sp_album_year() : api.h
  • sp_albumbrowse_add_ref() : api.h
  • sp_albumbrowse_album() : api.h
  • sp_albumbrowse_artist() : api.h
  • sp_albumbrowse_backend_request_duration() : api.h
  • sp_albumbrowse_copyright() : api.h
  • sp_albumbrowse_create() : api.h
  • sp_albumbrowse_error() : api.h
  • sp_albumbrowse_is_loaded() : api.h
  • sp_albumbrowse_num_copyrights() : api.h
  • sp_albumbrowse_num_tracks() : api.h
  • sp_albumbrowse_release() : api.h
  • sp_albumbrowse_review() : api.h
  • sp_albumbrowse_track() : api.h
  • sp_artist_add_ref() : api.h
  • sp_artist_is_loaded() : api.h
  • sp_artist_name() : api.h
  • sp_artist_portrait() : api.h
  • sp_artist_release() : api.h
  • sp_artistbrowse_add_ref() : api.h
  • sp_artistbrowse_album() : api.h
  • sp_artistbrowse_artist() : api.h
  • sp_artistbrowse_backend_request_duration() : api.h
  • sp_artistbrowse_biography() : api.h
  • sp_artistbrowse_create() : api.h
  • sp_artistbrowse_error() : api.h
  • sp_artistbrowse_is_loaded() : api.h
  • sp_artistbrowse_num_albums() : api.h
  • sp_artistbrowse_num_portraits() : api.h
  • sp_artistbrowse_num_similar_artists() : api.h
  • sp_artistbrowse_num_tophit_tracks() : api.h
  • sp_artistbrowse_num_tracks() : api.h
  • sp_artistbrowse_portrait() : api.h
  • sp_artistbrowse_release() : api.h
  • sp_artistbrowse_similar_artist() : api.h
  • sp_artistbrowse_tophit_track() : api.h
  • sp_artistbrowse_track() : api.h
  • sp_build_id() : api.h
  • sp_error_message() : api.h
  • sp_image_add_load_callback() : api.h
  • sp_image_add_ref() : api.h
  • sp_image_create() : api.h
  • sp_image_create_from_link() : api.h
  • sp_image_data() : api.h
  • sp_image_error() : api.h
  • sp_image_format() : api.h
  • sp_image_image_id() : api.h
  • sp_image_is_loaded() : api.h
  • sp_image_release() : api.h
  • sp_image_remove_load_callback() : api.h
  • sp_inbox_add_ref() : api.h
  • sp_inbox_error() : api.h
  • sp_inbox_post_tracks() : api.h
  • sp_inbox_release() : api.h
  • sp_link_add_ref() : api.h
  • sp_link_as_album() : api.h
  • sp_link_as_artist() : api.h
  • sp_link_as_string() : api.h
  • sp_link_as_track() : api.h
  • sp_link_as_track_and_offset() : api.h
  • sp_link_as_user() : api.h
  • sp_link_create_from_album() : api.h
  • sp_link_create_from_album_cover() : api.h
  • sp_link_create_from_artist() : api.h
  • sp_link_create_from_artist_portrait() : api.h
  • sp_link_create_from_artistbrowse_portrait() : api.h
  • sp_link_create_from_image() : api.h
  • sp_link_create_from_playlist() : api.h
  • sp_link_create_from_search() : api.h
  • sp_link_create_from_string() : api.h
  • sp_link_create_from_track() : api.h
  • sp_link_create_from_user() : api.h
  • sp_link_release() : api.h
  • sp_link_type() : api.h
  • sp_localtrack_create() : api.h
  • sp_offline_num_playlists() : api.h
  • sp_offline_sync_get_status() : api.h
  • sp_offline_time_left() : api.h
  • sp_offline_tracks_to_sync() : api.h
  • sp_playlist_add_callbacks() : api.h
  • sp_playlist_add_ref() : api.h
  • sp_playlist_add_tracks() : api.h
  • sp_playlist_create() : api.h
  • sp_playlist_get_description() : api.h
  • sp_playlist_get_image() : api.h
  • sp_playlist_get_offline_download_completed() : api.h
  • sp_playlist_get_offline_status() : api.h
  • sp_playlist_has_pending_changes() : api.h
  • sp_playlist_is_collaborative() : api.h
  • sp_playlist_is_in_ram() : api.h
  • sp_playlist_is_loaded() : api.h
  • sp_playlist_name() : api.h
  • sp_playlist_num_subscribers() : api.h
  • sp_playlist_num_tracks() : api.h
  • sp_playlist_owner() : api.h
  • sp_playlist_release() : api.h
  • sp_playlist_remove_callbacks() : api.h
  • sp_playlist_remove_tracks() : api.h
  • sp_playlist_rename() : api.h
  • sp_playlist_reorder_tracks() : api.h
  • sp_playlist_set_autolink_tracks() : api.h
  • sp_playlist_set_collaborative() : api.h
  • sp_playlist_set_in_ram() : api.h
  • sp_playlist_set_offline_mode() : api.h
  • sp_playlist_subscribers() : api.h
  • sp_playlist_subscribers_free() : api.h
  • sp_playlist_track() : api.h
  • sp_playlist_track_create_time() : api.h
  • sp_playlist_track_creator() : api.h
  • sp_playlist_track_message() : api.h
  • sp_playlist_track_seen() : api.h
  • sp_playlist_track_set_seen() : api.h
  • sp_playlist_update_subscribers() : api.h
  • sp_playlistcontainer_add_callbacks() : api.h
  • sp_playlistcontainer_add_folder() : api.h
  • sp_playlistcontainer_add_new_playlist() : api.h
  • sp_playlistcontainer_add_playlist() : api.h
  • sp_playlistcontainer_add_ref() : api.h
  • sp_playlistcontainer_clear_unseen_tracks() : api.h
  • sp_playlistcontainer_get_unseen_tracks() : api.h
  • sp_playlistcontainer_is_loaded() : api.h
  • sp_playlistcontainer_move_playlist() : api.h
  • sp_playlistcontainer_num_playlists() : api.h
  • sp_playlistcontainer_owner() : api.h
  • sp_playlistcontainer_playlist() : api.h
  • sp_playlistcontainer_playlist_folder_id() : api.h
  • sp_playlistcontainer_playlist_folder_name() : api.h
  • sp_playlistcontainer_playlist_type() : api.h
  • sp_playlistcontainer_release() : api.h
  • sp_playlistcontainer_remove_callbacks() : api.h
  • sp_playlistcontainer_remove_playlist() : api.h
  • sp_search_add_ref() : api.h
  • sp_search_album() : api.h
  • sp_search_artist() : api.h
  • sp_search_create() : api.h
  • sp_search_did_you_mean() : api.h
  • sp_search_error() : api.h
  • sp_search_is_loaded() : api.h
  • sp_search_num_albums() : api.h
  • sp_search_num_artists() : api.h
  • sp_search_num_playlists() : api.h
  • sp_search_num_tracks() : api.h
  • sp_search_playlist() : api.h
  • sp_search_playlist_image_uri() : api.h
  • sp_search_playlist_name() : api.h
  • sp_search_playlist_uri() : api.h
  • sp_search_query() : api.h
  • sp_search_release() : api.h
  • sp_search_total_albums() : api.h
  • sp_search_total_artists() : api.h
  • sp_search_total_playlists() : api.h
  • sp_search_total_tracks() : api.h
  • sp_search_track() : api.h
  • sp_session_connectionstate() : api.h
  • sp_session_create() : api.h
  • sp_session_flush_caches() : api.h
  • sp_session_forget_me() : api.h
  • sp_session_get_volume_normalization() : api.h
  • sp_session_inbox_create() : api.h
  • sp_session_is_private_session() : api.h
  • sp_session_is_scrobbling() : api.h
  • sp_session_is_scrobbling_possible() : api.h
  • sp_session_login() : api.h
  • sp_session_logout() : api.h
  • sp_session_player_load() : api.h
  • sp_session_player_play() : api.h
  • sp_session_player_prefetch() : api.h
  • sp_session_player_seek() : api.h
  • sp_session_player_unload() : api.h
  • sp_session_playlistcontainer() : api.h
  • sp_session_preferred_bitrate() : api.h
  • sp_session_preferred_offline_bitrate() : api.h
  • sp_session_process_events() : api.h
  • sp_session_publishedcontainer_for_user_create() : api.h
  • sp_session_release() : api.h
  • sp_session_relogin() : api.h
  • sp_session_remembered_user() : api.h
  • sp_session_set_cache_size() : api.h
  • sp_session_set_connection_rules() : api.h
  • sp_session_set_connection_type() : api.h
  • sp_session_set_private_session() : api.h
  • sp_session_set_scrobbling() : api.h
  • sp_session_set_social_credentials() : api.h
  • sp_session_set_volume_normalization() : api.h
  • sp_session_starred_create() : api.h
  • sp_session_starred_for_user_create() : api.h
  • sp_session_user() : api.h
  • sp_session_user_country() : api.h
  • sp_session_user_name() : api.h
  • sp_session_userdata() : api.h
  • sp_toplistbrowse_add_ref() : api.h
  • sp_toplistbrowse_album() : api.h
  • sp_toplistbrowse_artist() : api.h
  • sp_toplistbrowse_backend_request_duration() : api.h
  • sp_toplistbrowse_create() : api.h
  • sp_toplistbrowse_error() : api.h
  • sp_toplistbrowse_is_loaded() : api.h
  • sp_toplistbrowse_num_albums() : api.h
  • sp_toplistbrowse_num_artists() : api.h
  • sp_toplistbrowse_num_tracks() : api.h
  • sp_toplistbrowse_release() : api.h
  • sp_toplistbrowse_track() : api.h
  • sp_track_add_ref() : api.h
  • sp_track_album() : api.h
  • sp_track_artist() : api.h
  • sp_track_disc() : api.h
  • sp_track_duration() : api.h
  • sp_track_error() : api.h
  • sp_track_get_availability() : api.h
  • sp_track_get_playable() : api.h
  • sp_track_index() : api.h
  • sp_track_is_autolinked() : api.h
  • sp_track_is_loaded() : api.h
  • sp_track_is_local() : api.h
  • sp_track_is_placeholder() : api.h
  • sp_track_is_starred() : api.h
  • sp_track_name() : api.h
  • sp_track_num_artists() : api.h
  • sp_track_offline_get_status() : api.h
  • sp_track_popularity() : api.h
  • sp_track_release() : api.h
  • sp_track_set_starred() : api.h
  • sp_user_add_ref() : api.h
  • sp_user_canonical_name() : api.h
  • sp_user_display_name() : api.h
  • sp_user_is_loaded() : api.h
  • sp_user_release() : api.h

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/globals_type.html ================================================ libspotify: Data Fields
 

- a -

  • albumbrowse_complete_cb : api.h
  • artistbrowse_complete_cb : api.h

- i -

  • image_loaded_cb : api.h
  • inboxpost_complete_cb : api.h

- s -

- t -

  • toplistbrowse_complete_cb : api.h

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__album.html ================================================ libspotify: Album subsystem

Album subsystem

Enumerations

enum  sp_albumtype {
  SP_ALBUMTYPE_ALBUM = 0,
  SP_ALBUMTYPE_SINGLE = 1,
  SP_ALBUMTYPE_COMPILATION = 2,
  SP_ALBUMTYPE_UNKNOWN = 3
}

Functions

bool sp_album_is_loaded (sp_album *album)
bool sp_album_is_available (sp_album *album)
sp_artistsp_album_artist (sp_album *album)
const byte * sp_album_cover (sp_album *album, sp_image_size size)
const char * sp_album_name (sp_album *album)
int sp_album_year (sp_album *album)
sp_albumtype sp_album_type (sp_album *album)
sp_error sp_album_add_ref (sp_album *album)
sp_error sp_album_release (sp_album *album)

Enumeration Type Documentation

Album types

Enumerator:
SP_ALBUMTYPE_ALBUM 

Normal album.

SP_ALBUMTYPE_SINGLE 

Single.

SP_ALBUMTYPE_COMPILATION 

Compilation.

SP_ALBUMTYPE_UNKNOWN 

Unknown type.


Function Documentation

sp_error sp_album_add_ref ( sp_album album  ) 

Increase the reference count of an album

Parameters:
[in] album The album object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_artist* sp_album_artist ( sp_album album  ) 

Get the artist associated with the given album

Parameters:
[in] album Album object
Returns:
A reference to the artist. NULL if the metadata has not been loaded yet
Examples:
toplist.c.
const byte* sp_album_cover ( sp_album album,
sp_image_size  size 
)

Return image ID representing the album's coverart.

Parameters:
[in] album Album object
[in] size The desired size of the image
Returns:
ID byte sequence that can be passed to sp_image_create() If the album has no image or the metadata for the album is not loaded yet, this function returns NULL.
See also:
sp_image_create
bool sp_album_is_available ( sp_album album  ) 

Return true if the album is available in the current region.

Parameters:
[in] album The album
Returns:
True if album is available for playback, otherwise false.
Note:
The album must be loaded or this function will always return false.
See also:
sp_album_is_loaded()
bool sp_album_is_loaded ( sp_album album  ) 

Check if the album object is populated with data

Parameters:
[in] album Album object
Returns:
True if metadata is present, false if not
const char* sp_album_name ( sp_album album  ) 

Return name of album

Parameters:
[in] album Album object
Returns:
Name of album. Returned string is valid as long as the album object stays allocated and no longer than the next call to sp_session_process_events()
Examples:
browse.c, search.c, and toplist.c.
sp_error sp_album_release ( sp_album album  ) 

Decrease the reference count of an album

Parameters:
[in] album The album object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_albumtype sp_album_type ( sp_album album  ) 

Return type of specified album

Parameters:
[in] album Album object
Returns:
sp_albumtype
int sp_album_year ( sp_album album  ) 

Return release year of specified album

Parameters:
[in] album Album object
Returns:
Release year
Examples:
browse.c, and search.c.

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__albumbrowse.html ================================================ libspotify: Album browsing

Album browsing

Typedefs

typedef void albumbrowse_complete_cb (sp_albumbrowse *result, void *userdata)

Functions

sp_albumbrowsesp_albumbrowse_create (sp_session *session, sp_album *album, albumbrowse_complete_cb *callback, void *userdata)
bool sp_albumbrowse_is_loaded (sp_albumbrowse *alb)
sp_error sp_albumbrowse_error (sp_albumbrowse *alb)
sp_albumsp_albumbrowse_album (sp_albumbrowse *alb)
sp_artistsp_albumbrowse_artist (sp_albumbrowse *alb)
int sp_albumbrowse_num_copyrights (sp_albumbrowse *alb)
const char * sp_albumbrowse_copyright (sp_albumbrowse *alb, int index)
int sp_albumbrowse_num_tracks (sp_albumbrowse *alb)
sp_tracksp_albumbrowse_track (sp_albumbrowse *alb, int index)
const char * sp_albumbrowse_review (sp_albumbrowse *alb)
int sp_albumbrowse_backend_request_duration (sp_albumbrowse *alb)
sp_error sp_albumbrowse_add_ref (sp_albumbrowse *alb)
sp_error sp_albumbrowse_release (sp_albumbrowse *alb)

Detailed Description

Browsing adds additional information to what an sp_album holds. It retrieves copyrights, reviews and tracks of the album.


Typedef Documentation

typedef void albumbrowse_complete_cb(sp_albumbrowse *result, void *userdata)

The type of a callback used in sp_albumbrowse_create()

When the callback is called, the metadata of all tracks belonging to it will have been loaded, so sp_track_is_loaded() will return non-zero. The sp_artist of the album will also have been fully loaded.

Parameters:
[in] result The same pointer returned by sp_albumbrowse_create()
[in] userdata The opaque pointer given to sp_albumbrowse_create()

Function Documentation

sp_error sp_albumbrowse_add_ref ( sp_albumbrowse alb  ) 

Increase the reference count of an album browse result

Parameters:
[in] alb The album browse result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_album* sp_albumbrowse_album ( sp_albumbrowse alb  ) 

Given an album browse object, return the pointer to its album object

Parameters:
[in] alb Album browse object
Returns:
Album object
Examples:
browse.c.
sp_artist* sp_albumbrowse_artist ( sp_albumbrowse alb  ) 

Given an album browse object, return the pointer to its artist object

Parameters:
[in] alb Album browse object
Returns:
Artist object
int sp_albumbrowse_backend_request_duration ( sp_albumbrowse alb  ) 

Return the time (in ms) that was spent waiting for the Spotify backend to serve the request

Parameters:
[in] alb Album browse object
Returns:
-1 if the request was served from the local cache If the result is not yet loaded the return value is undefined
const char* sp_albumbrowse_copyright ( sp_albumbrowse alb,
int  index 
)

Given an album browse object, return one of its copyright strings

Parameters:
[in] alb Album browse object
[in] index The index for the copyright string. Should be in the interval [0, sp_albumbrowse_num_copyrights() - 1]
Returns:
Copyright string in UTF-8 format, or NULL if the index is invalid. Returned string is valid as long as the album object stays allocated and no longer than the next call to sp_session_process_events()
Examples:
browse.c.
sp_albumbrowse* sp_albumbrowse_create ( sp_session session,
sp_album album,
albumbrowse_complete_cb callback,
void *  userdata 
)

Initiate a request for browsing an album

The user is responsible for freeing the returned album browse using sp_albumbrowse_release(). This can be done in the callback.

Parameters:
[in] session Session object
[in] album Album to be browsed. The album metadata does not have to be loaded
[in] callback Callback to be invoked when browsing has been completed. Pass NULL if you are not interested in this event.
[in] userdata Userdata passed to callback.
Returns:
Album browse object
See also:
albumbrowse_complete_cb
Examples:
browse.c.
sp_error sp_albumbrowse_error ( sp_albumbrowse alb  ) 

Check if browsing returned an error code.

Parameters:
[in] alb Album browse object
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_IS_LOADING SP_ERROR_OTHER_PERMANENT SP_ERROR_OTHER_TRANSIENT
Examples:
browse.c.
bool sp_albumbrowse_is_loaded ( sp_albumbrowse alb  ) 

Check if an album browse request is completed

Parameters:
[in] alb Album browse object
Returns:
True if browsing is completed, false if not
int sp_albumbrowse_num_copyrights ( sp_albumbrowse alb  ) 

Given an album browse object, return number of copyright strings

Parameters:
[in] alb Album browse object
Returns:
Number of copyright strings available, 0 if unknown
Examples:
browse.c.
int sp_albumbrowse_num_tracks ( sp_albumbrowse alb  ) 

Given an album browse object, return number of tracks

Parameters:
[in] alb Album browse object
Returns:
Number of tracks on album
Examples:
browse.c.
sp_error sp_albumbrowse_release ( sp_albumbrowse alb  ) 

Decrease the reference count of an album browse result

Parameters:
[in] alb The album browse result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
browse.c.
const char* sp_albumbrowse_review ( sp_albumbrowse alb  ) 

Given an album browse object, return its review

Parameters:
[in] alb Album browse object
Returns:
Review string in UTF-8 format. Returned string is valid as long as the album object stays allocated and no longer than the next call to sp_session_process_events()
Examples:
browse.c.
sp_track* sp_albumbrowse_track ( sp_albumbrowse alb,
int  index 
)

Given an album browse object, return a pointer to one of its tracks

Parameters:
[in] alb Album browse object
[in] index The index for the track. Should be in the interval [0, sp_albumbrowse_num_tracks() - 1]
Returns:
A track.
See also:
Track subsystem
Examples:
browse.c.

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__artist.html ================================================ libspotify: Artist subsystem

Artist subsystem

Functions

const char * sp_artist_name (sp_artist *artist)
bool sp_artist_is_loaded (sp_artist *artist)
const byte * sp_artist_portrait (sp_artist *artist, sp_image_size size)
sp_error sp_artist_add_ref (sp_artist *artist)
sp_error sp_artist_release (sp_artist *artist)

Function Documentation

sp_error sp_artist_add_ref ( sp_artist artist  ) 

Increase the reference count of a artist

Parameters:
[in] artist The artist object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
bool sp_artist_is_loaded ( sp_artist artist  ) 

Check if the artist object is populated with data

Parameters:
[in] artist An artist object
Returns:
True if metadata is present, false if not
const char* sp_artist_name ( sp_artist artist  ) 

Return name of artist

Parameters:
[in] artist Artist object
Returns:
Name of artist. Returned string is valid as long as the artist object stays allocated and no longer than the next call to sp_session_process_events()
Examples:
browse.c, search.c, and toplist.c.
const byte* sp_artist_portrait ( sp_artist artist,
sp_image_size  size 
)

Return portrait for artist

Parameters:
[in] artist The artist object
[in] size The desired size of the image
Returns:
ID byte sequence that can be passed to sp_image_create() If the artist has no image or the metadata for the album is not loaded yet, this function returns NULL.
sp_error sp_artist_release ( sp_artist artist  ) 

Decrease the reference count of a artist

Parameters:
[in] artist The artist object
Returns:
One of the following errors, from sp_error SP_ERROR_OK

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__artistbrowse.html ================================================ libspotify: Artist browsing

Artist browsing

Typedefs

typedef void artistbrowse_complete_cb (sp_artistbrowse *result, void *userdata)

Functions

sp_artistbrowsesp_artistbrowse_create (sp_session *session, sp_artist *artist, sp_artistbrowse_type type, artistbrowse_complete_cb *callback, void *userdata)
bool sp_artistbrowse_is_loaded (sp_artistbrowse *arb)
sp_error sp_artistbrowse_error (sp_artistbrowse *arb)
sp_artistsp_artistbrowse_artist (sp_artistbrowse *arb)
int sp_artistbrowse_num_portraits (sp_artistbrowse *arb)
const byte * sp_artistbrowse_portrait (sp_artistbrowse *arb, int index)
int sp_artistbrowse_num_tracks (sp_artistbrowse *arb)
sp_tracksp_artistbrowse_track (sp_artistbrowse *arb, int index)
int sp_artistbrowse_num_tophit_tracks (sp_artistbrowse *arb)
sp_tracksp_artistbrowse_tophit_track (sp_artistbrowse *arb, int index)
int sp_artistbrowse_num_albums (sp_artistbrowse *arb)
sp_albumsp_artistbrowse_album (sp_artistbrowse *arb, int index)
int sp_artistbrowse_num_similar_artists (sp_artistbrowse *arb)
sp_artistsp_artistbrowse_similar_artist (sp_artistbrowse *arb, int index)
const char * sp_artistbrowse_biography (sp_artistbrowse *arb)
int sp_artistbrowse_backend_request_duration (sp_artistbrowse *arb)
sp_error sp_artistbrowse_add_ref (sp_artistbrowse *arb)
sp_error sp_artistbrowse_release (sp_artistbrowse *arb)

Detailed Description

Artist browsing initiates the fetching of information for a certain artist.

Note:
There is currently no built-in functionality available for getting the albums belonging to an artist. For now, just iterate over all tracks and note the album to build a list of all albums. This feature will be added in a future version of the library.

Typedef Documentation

typedef void artistbrowse_complete_cb(sp_artistbrowse *result, void *userdata)

The type of a callback used in sp_artistbrowse_create()

When the callback is called, the metadata of all tracks belonging to it will have been loaded, so sp_track_is_loaded() will return non-zero. The same goes for the similar artist data.

Parameters:
[in] result The same pointer returned by sp_artistbrowse_create()
[in] userdata The opaque pointer given to sp_artistbrowse_create()

Function Documentation

sp_error sp_artistbrowse_add_ref ( sp_artistbrowse arb  ) 

Increase the reference count of an artist browse result

Parameters:
[in] arb The artist browse result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_album* sp_artistbrowse_album ( sp_artistbrowse arb,
int  index 
)

Given an artist browse object, return one of its albums

Parameters:
[in] arb Album browse object
[in] index The index for the album. Should be in the interval [0, sp_artistbrowse_num_albums() - 1]
Returns:
A album object, or NULL if the index is out of range.
See also:
Album subsystem
sp_artist* sp_artistbrowse_artist ( sp_artistbrowse arb  ) 

Given an artist browse object, return a pointer to its artist object

Parameters:
[in] arb Artist browse object
Returns:
Artist object
Examples:
browse.c.
int sp_artistbrowse_backend_request_duration ( sp_artistbrowse arb  ) 

Return the time (in ms) that was spent waiting for the Spotify backend to serve the request

Parameters:
[in] arb Artist browse object
Returns:
-1 if the request was served from the local cache If the result is not yet loaded the return value is undefined
const char* sp_artistbrowse_biography ( sp_artistbrowse arb  ) 

Given an artist browse object, return the artists biography

Note:
This function must be called from the same thread that did sp_session_create()
Parameters:
[in] arb Artist browse object
Returns:
Biography string in UTF-8 format. Returned string is valid as long as the album object stays allocated and no longer than the next call to sp_session_process_events()
Examples:
browse.c.
sp_artistbrowse* sp_artistbrowse_create ( sp_session session,
sp_artist artist,
sp_artistbrowse_type  type,
artistbrowse_complete_cb callback,
void *  userdata 
)

Initiate a request for browsing an artist

The user is responsible for freeing the returned artist browse using sp_artistbrowse_release(). This can be done in the callback.

Parameters:
[in] session Session object
[in] artist Artist to be browsed. The artist metadata does not have to be loaded
[in] type Type of data requested, see the sp_artistbrowse_type enum for details
[in] callback Callback to be invoked when browsing has been completed. Pass NULL if you are not interested in this event.
[in] userdata Userdata passed to callback.
Returns:
Artist browse object
See also:
artistbrowse_complete_cb
Examples:
browse.c.
sp_error sp_artistbrowse_error ( sp_artistbrowse arb  ) 

Check if browsing returned an error code.

Parameters:
[in] arb Artist browse object
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_IS_LOADING SP_ERROR_OTHER_PERMANENT SP_ERROR_OTHER_TRANSIENT
Examples:
browse.c.
bool sp_artistbrowse_is_loaded ( sp_artistbrowse arb  ) 

Check if an artist browse request is completed

Parameters:
[in] arb Artist browse object
Returns:
True if browsing is completed, false if not
int sp_artistbrowse_num_albums ( sp_artistbrowse arb  ) 

Given an artist browse object, return number of albums

Parameters:
[in] arb Artist browse object
Returns:
Number of albums for given artist
int sp_artistbrowse_num_portraits ( sp_artistbrowse arb  ) 

Given an artist browse object, return number of portraits available

Parameters:
[in] arb Artist browse object
Returns:
Number of portraits for given artist
Examples:
browse.c.
int sp_artistbrowse_num_similar_artists ( sp_artistbrowse arb  ) 

Given an artist browse object, return number of similar artists

Parameters:
[in] arb Artist browse object
Returns:
Number of similar artists for given artist
Examples:
browse.c.
int sp_artistbrowse_num_tophit_tracks ( sp_artistbrowse arb  ) 

Given an artist browse object, return number of tophit tracks This is a set of tracks for the artist with highest popularity

Parameters:
[in] arb Artist browse object
Returns:
Number of tophit tracks for given artist
int sp_artistbrowse_num_tracks ( sp_artistbrowse arb  ) 

Given an artist browse object, return number of tracks

Parameters:
[in] arb Artist browse object
Returns:
Number of tracks for given artist
Examples:
browse.c.
const byte* sp_artistbrowse_portrait ( sp_artistbrowse arb,
int  index 
)

Return image ID representing a portrait of the artist

Parameters:
[in] arb Artist object
[in] index The index of the portrait. Should be in the interval [0, sp_artistbrowse_num_portraits() - 1]
Returns:
ID byte sequence that can be passed to sp_image_create()
See also:
sp_image_create
sp_error sp_artistbrowse_release ( sp_artistbrowse arb  ) 

Decrease the reference count of an artist browse result

Parameters:
[in] arb The artist browse result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
browse.c.
sp_artist* sp_artistbrowse_similar_artist ( sp_artistbrowse arb,
int  index 
)

Given an artist browse object, return a similar artist by index

Parameters:
[in] arb Album browse object
[in] index The index for the artist. Should be in the interval [0, sp_artistbrowse_num_similar_artists() - 1]
Returns:
A pointer to an artist object.
See also:
Artist subsystem
Examples:
browse.c.
sp_track* sp_artistbrowse_tophit_track ( sp_artistbrowse arb,
int  index 
)

Given an artist browse object, return one of its tophit tracks This is a set of tracks for the artist with highest popularity

Parameters:
[in] arb Album browse object
[in] index The index for the track. Should be in the interval [0, sp_artistbrowse_num_tophit_tracks() - 1]
Returns:
A track object, or NULL if the index is out of range.
See also:
Track subsystem
sp_track* sp_artistbrowse_track ( sp_artistbrowse arb,
int  index 
)

Given an artist browse object, return one of its tracks

Parameters:
[in] arb Album browse object
[in] index The index for the track. Should be in the interval [0, sp_artistbrowse_num_tracks() - 1]
Returns:
A track object, or NULL if the index is out of range.
See also:
Track subsystem
Examples:
browse.c.

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__error.html ================================================ libspotify: Error Handling

Error Handling

Typedefs

typedef enum sp_error sp_error

Enumerations

enum  sp_error {
  SP_ERROR_OK = 0,
  SP_ERROR_BAD_API_VERSION = 1,
  SP_ERROR_API_INITIALIZATION_FAILED = 2,
  SP_ERROR_TRACK_NOT_PLAYABLE = 3,
  SP_ERROR_BAD_APPLICATION_KEY = 5,
  SP_ERROR_BAD_USERNAME_OR_PASSWORD = 6,
  SP_ERROR_USER_BANNED = 7,
  SP_ERROR_UNABLE_TO_CONTACT_SERVER = 8,
  SP_ERROR_CLIENT_TOO_OLD = 9,
  SP_ERROR_OTHER_PERMANENT = 10,
  SP_ERROR_BAD_USER_AGENT = 11,
  SP_ERROR_MISSING_CALLBACK = 12,
  SP_ERROR_INVALID_INDATA = 13,
  SP_ERROR_INDEX_OUT_OF_RANGE = 14,
  SP_ERROR_USER_NEEDS_PREMIUM = 15,
  SP_ERROR_OTHER_TRANSIENT = 16,
  SP_ERROR_IS_LOADING = 17,
  SP_ERROR_NO_STREAM_AVAILABLE = 18,
  SP_ERROR_PERMISSION_DENIED = 19,
  SP_ERROR_INBOX_IS_FULL = 20,
  SP_ERROR_NO_CACHE = 21,
  SP_ERROR_NO_SUCH_USER = 22,
  SP_ERROR_NO_CREDENTIALS = 23,
  SP_ERROR_NETWORK_DISABLED = 24,
  SP_ERROR_INVALID_DEVICE_ID = 25,
  SP_ERROR_CANT_OPEN_TRACE_FILE = 26,
  SP_ERROR_APPLICATION_BANNED = 27,
  SP_ERROR_OFFLINE_TOO_MANY_TRACKS = 31,
  SP_ERROR_OFFLINE_DISK_CACHE = 32,
  SP_ERROR_OFFLINE_EXPIRED = 33,
  SP_ERROR_OFFLINE_NOT_ALLOWED = 34,
  SP_ERROR_OFFLINE_LICENSE_LOST = 35,
  SP_ERROR_OFFLINE_LICENSE_ERROR = 36,
  SP_ERROR_LASTFM_AUTH_ERROR = 39,
  SP_ERROR_INVALID_ARGUMENT = 40,
  SP_ERROR_SYSTEM_FAILURE = 41
}

Functions

const char * sp_error_message (sp_error error)

Detailed Description

All functions in libspotify use the same set of error codes. Most of them return an error code, and let the result of the operation be stored in an out-parameter.


Typedef Documentation

typedef enum sp_error sp_error

Error codes returned by various functions


Enumeration Type Documentation

enum sp_error

Error codes returned by various functions

Enumerator:
SP_ERROR_OK 

No errors encountered.

SP_ERROR_BAD_API_VERSION 

The library version targeted does not match the one you claim you support.

SP_ERROR_API_INITIALIZATION_FAILED 

Initialization of library failed - are cache locations etc. valid?

SP_ERROR_TRACK_NOT_PLAYABLE 

The track specified for playing cannot be played.

SP_ERROR_BAD_APPLICATION_KEY 

The application key is invalid.

SP_ERROR_BAD_USERNAME_OR_PASSWORD 

Login failed because of bad username and/or password.

SP_ERROR_USER_BANNED 

The specified username is banned.

SP_ERROR_UNABLE_TO_CONTACT_SERVER 

Cannot connect to the Spotify backend system.

SP_ERROR_CLIENT_TOO_OLD 

Client is too old, library will need to be updated.

SP_ERROR_OTHER_PERMANENT 

Some other error occurred, and it is permanent (e.g. trying to relogin will not help).

SP_ERROR_BAD_USER_AGENT 

The user agent string is invalid or too long.

SP_ERROR_MISSING_CALLBACK 

No valid callback registered to handle events.

SP_ERROR_INVALID_INDATA 

Input data was either missing or invalid.

SP_ERROR_INDEX_OUT_OF_RANGE 

Index out of range.

SP_ERROR_USER_NEEDS_PREMIUM 

The specified user needs a premium account.

SP_ERROR_OTHER_TRANSIENT 

A transient error occurred.

SP_ERROR_IS_LOADING 

The resource is currently loading.

SP_ERROR_NO_STREAM_AVAILABLE 

Could not find any suitable stream to play.

SP_ERROR_PERMISSION_DENIED 

Requested operation is not allowed.

SP_ERROR_INBOX_IS_FULL 

Target inbox is full.

SP_ERROR_NO_CACHE 

Cache is not enabled.

SP_ERROR_NO_SUCH_USER 

Requested user does not exist.

SP_ERROR_NO_CREDENTIALS 

No credentials are stored.

SP_ERROR_NETWORK_DISABLED 

Network disabled.

SP_ERROR_INVALID_DEVICE_ID 

Invalid device ID.

SP_ERROR_CANT_OPEN_TRACE_FILE 

Unable to open trace file.

SP_ERROR_APPLICATION_BANNED 

This application is no longer allowed to use the Spotify service.

SP_ERROR_OFFLINE_TOO_MANY_TRACKS 

Reached the device limit for number of tracks to download.

SP_ERROR_OFFLINE_DISK_CACHE 

Disk cache is full so no more tracks can be downloaded to offline mode.

SP_ERROR_OFFLINE_EXPIRED 

Offline key has expired, the user needs to go online again.

SP_ERROR_OFFLINE_NOT_ALLOWED 

This user is not allowed to use offline mode.

SP_ERROR_OFFLINE_LICENSE_LOST 

The license for this device has been lost. Most likely because the user used offline on three other device.

SP_ERROR_OFFLINE_LICENSE_ERROR 

The Spotify license server does not respond correctly.

SP_ERROR_LASTFM_AUTH_ERROR 

A LastFM scrobble authentication error has occurred.

SP_ERROR_INVALID_ARGUMENT 

An invalid argument was specified.

SP_ERROR_SYSTEM_FAILURE 

An operating system error.


Function Documentation

const char* sp_error_message ( sp_error  error  ) 

Convert a numeric libspotify error code to a text string. The error message is in English. This function is useful for logging purposes.

Parameters:
[in] error The error code to lookup
Examples:
browse.c, jukebox.c, and search.c.

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__image.html ================================================ libspotify: Image handling

Image handling

Typedefs

typedef void image_loaded_cb (sp_image *image, void *userdata)

Enumerations

enum  sp_imageformat {
  SP_IMAGE_FORMAT_UNKNOWN = -1,
  SP_IMAGE_FORMAT_JPEG = 0
}

Functions

sp_imagesp_image_create (sp_session *session, const byte image_id[20])
sp_imagesp_image_create_from_link (sp_session *session, sp_link *l)
sp_error sp_image_add_load_callback (sp_image *image, image_loaded_cb *callback, void *userdata)
sp_error sp_image_remove_load_callback (sp_image *image, image_loaded_cb *callback, void *userdata)
bool sp_image_is_loaded (sp_image *image)
sp_error sp_image_error (sp_image *image)
sp_imageformat sp_image_format (sp_image *image)
const void * sp_image_data (sp_image *image, size_t *data_size)
const byte * sp_image_image_id (sp_image *image)
sp_error sp_image_add_ref (sp_image *image)
sp_error sp_image_release (sp_image *image)

Typedef Documentation

typedef void image_loaded_cb(sp_image *image, void *userdata)

The type of a callback used to notify the application that an image is done loading.


Enumeration Type Documentation

Image format

Enumerator:
SP_IMAGE_FORMAT_UNKNOWN 

Unknown image format.

SP_IMAGE_FORMAT_JPEG 

JPEG image.


Function Documentation

sp_error sp_image_add_load_callback ( sp_image image,
image_loaded_cb callback,
void *  userdata 
)

Add a callback that will be invoked when the image is loaded

If an image is loaded, and loading fails, the image will behave like an empty image.

Parameters:
[in] image Image object
[in] callback Callback that will be called when image has been fetched.
[in] userdata Opaque pointer passed to callback
sp_error sp_image_add_ref ( sp_image image  ) 

Increase the reference count of an image

Parameters:
[in] image The image object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_image* sp_image_create ( sp_session session,
const byte  image_id[20] 
)

Create an image object

Parameters:
[in] session Session
[in] image_id Spotify image ID
Returns:
Pointer to an image object. To free the object, use sp_image_release()
See also:
sp_album_cover
sp_artistbrowse_portrait
sp_image* sp_image_create_from_link ( sp_session session,
sp_link l 
)

Create an image object from a link

Parameters:
[in] session Session
[in] l Spotify link object. This must be of SP_LINKTYPE_IMAGE type
Returns:
Pointer to an image object. To free the object, use sp_image_release()
See also:
sp_image_create
const void* sp_image_data ( sp_image image,
size_t *  data_size 
)

Get image data

Parameters:
[in] image Image object
[out] data_size Size of raw image data
Returns:
Pointer to raw image data
sp_error sp_image_error ( sp_image image  ) 

Check if image retrieval returned an error code.

Parameters:
[in] image Image object
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_IS_LOADING SP_ERROR_OTHER_PERMANENT SP_ERROR_OTHER_TRANSIENT
sp_imageformat sp_image_format ( sp_image image  ) 

Get image format

Parameters:
[in] image Image object
Returns:
Image format as described by sp_imageformat
const byte* sp_image_image_id ( sp_image image  ) 

Get image ID

Parameters:
[in] image Image object
Returns:
Image ID
bool sp_image_is_loaded ( sp_image image  ) 

Check if an image is loaded. Before the image is loaded, the rest of the methods will behave as if the image is empty.

Parameters:
[in] image Image object
Returns:
True if image is loaded, false otherwise
sp_error sp_image_release ( sp_image image  ) 

Decrease the reference count of an image

Parameters:
[in] image The image object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_image_remove_load_callback ( sp_image image,
image_loaded_cb callback,
void *  userdata 
)

Remove an image load callback previously added with sp_image_add_load_callback()

Parameters:
[in] image Image object
[in] callback Callback that will not be called when image has been fetched.
[in] userdata Opaque pointer passed to callback
Returns:
One of the following errors, from sp_error SP_ERROR_OK

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__inbox.html ================================================ libspotify: Inbox subsystem

Inbox subsystem

Typedefs

typedef void inboxpost_complete_cb (sp_inbox *result, void *userdata)

Functions

sp_inboxsp_inbox_post_tracks (sp_session *session, const char *user, sp_track *const *tracks, int num_tracks, const char *message, inboxpost_complete_cb *callback, void *userdata)
sp_error sp_inbox_error (sp_inbox *inbox)
sp_error sp_inbox_add_ref (sp_inbox *inbox)
sp_error sp_inbox_release (sp_inbox *inbox)

Typedef Documentation

typedef void inboxpost_complete_cb(sp_inbox *result, void *userdata)

The type of a callback used in sp_inbox_post()

When this callback is called, the sp_track_is_loaded(), sp_album_is_loaded(), and sp_artist_is_loaded() functions will return non-zero for the objects contained in the search result.

Parameters:
[in] result The same pointer returned by sp_search_create()
[in] userdata The opaque pointer given to sp_search_create()

Function Documentation

sp_error sp_inbox_add_ref ( sp_inbox inbox  ) 

Increase the reference count of a inbox result

Parameters:
[in] inbox The inbox result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_inbox_error ( sp_inbox inbox  ) 

Check if inbox operation returned an error code.

Parameters:
[in] inbox Inbox object
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_OTHER_TRANSIENT SP_ERROR_PERMISSION_DENIED SP_ERROR_INVALID_INDATA SP_ERROR_INBOX_IS_FULL SP_ERROR_NO_SUCH_USER SP_ERROR_OTHER_PERMANENT
sp_inbox* sp_inbox_post_tracks ( sp_session session,
const char *  user,
sp_track *const *  tracks,
int  num_tracks,
const char *  message,
inboxpost_complete_cb callback,
void *  userdata 
)

Add to inbox

Parameters:
[in] session Session object
[in] user Canonical username of recipient
[in] tracks Array of tracks to post
[in] num_tracks Number of tracks in tracks
[in] message Message to attach to tracks. UTF-8
[in] callback Callback to be invoked when the request has completed
[in] userdata Userdata passed to callback
Returns:
sp_inbox object if the request has been sent, NULL if request failed to initialize
sp_error sp_inbox_release ( sp_inbox inbox  ) 

Decrease the reference count of a inbox result

Parameters:
[in] inbox The inbox result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__link.html ================================================ libspotify: Links (Spotify URIs)

Links (Spotify URIs)

Enumerations

enum  sp_linktype {
  SP_LINKTYPE_INVALID = 0,
  SP_LINKTYPE_TRACK = 1,
  SP_LINKTYPE_ALBUM = 2,
  SP_LINKTYPE_ARTIST = 3,
  SP_LINKTYPE_SEARCH = 4,
  SP_LINKTYPE_PLAYLIST = 5,
  SP_LINKTYPE_PROFILE = 6,
  SP_LINKTYPE_STARRED = 7,
  SP_LINKTYPE_LOCALTRACK = 8,
  SP_LINKTYPE_IMAGE = 9
}

Functions

sp_linksp_link_create_from_string (const char *link)
sp_linksp_link_create_from_track (sp_track *track, int offset)
sp_linksp_link_create_from_album (sp_album *album)
sp_linksp_link_create_from_album_cover (sp_album *album, sp_image_size size)
sp_linksp_link_create_from_artist (sp_artist *artist)
sp_linksp_link_create_from_artist_portrait (sp_artist *artist, sp_image_size size)
sp_linksp_link_create_from_artistbrowse_portrait (sp_artistbrowse *arb, int index)
sp_linksp_link_create_from_search (sp_search *search)
sp_linksp_link_create_from_playlist (sp_playlist *playlist)
sp_linksp_link_create_from_user (sp_user *user)
sp_linksp_link_create_from_image (sp_image *image)
int sp_link_as_string (sp_link *link, char *buffer, int buffer_size)
sp_linktype sp_link_type (sp_link *link)
sp_tracksp_link_as_track (sp_link *link)
sp_tracksp_link_as_track_and_offset (sp_link *link, int *offset)
sp_albumsp_link_as_album (sp_link *link)
sp_artistsp_link_as_artist (sp_link *link)
sp_usersp_link_as_user (sp_link *link)
sp_error sp_link_add_ref (sp_link *link)
sp_error sp_link_release (sp_link *link)

Detailed Description

These functions handle links to Spotify entities in a way that allows you to not care about the textual representation of the link.


Enumeration Type Documentation

Link types

Enumerator:
SP_LINKTYPE_INVALID 

Link type not valid - default until the library has parsed the link, or when parsing failed.

SP_LINKTYPE_TRACK 

Link type is track.

SP_LINKTYPE_ALBUM 

Link type is album.

SP_LINKTYPE_ARTIST 

Link type is artist.

SP_LINKTYPE_SEARCH 

Link type is search.

SP_LINKTYPE_PLAYLIST 

Link type is playlist.

SP_LINKTYPE_PROFILE 

Link type is profile.

SP_LINKTYPE_STARRED 

Link type is starred.

SP_LINKTYPE_LOCALTRACK 

Link type is a local file.

SP_LINKTYPE_IMAGE 

Link type is an image.


Function Documentation

sp_error sp_link_add_ref ( sp_link link  ) 

Increase the reference count of a link

Parameters:
[in] link The link object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_album* sp_link_as_album ( sp_link link  ) 

The album representation for the given link

Parameters:
[in] link The Spotify link whose album you are interested in
Returns:
The album representation of the given album link If the link is not of album type then NULL is returned
Examples:
browse.c.
sp_artist* sp_link_as_artist ( sp_link link  ) 

The artist representation for the given link

Parameters:
[in] link The Spotify link whose artist you are interested in
Returns:
The artist representation of the given link If the link is not of artist type then NULL is returned
Examples:
browse.c.
int sp_link_as_string ( sp_link link,
char *  buffer,
int  buffer_size 
)

Create a string representation of the given Spotify link

Parameters:
[in] link The Spotify link whose string representation you are interested in
[out] buffer The buffer to hold the string representation of link
[in] buffer_size The max size of the buffer that will hold the string representation The resulting string is guaranteed to always be null terminated if buffer_size > 0
Returns:
The number of characters in the string representation of the link. If this value is greater or equal than buffer_size, output was truncated.
Examples:
browse.c, and toplist.c.
sp_track* sp_link_as_track ( sp_link link  ) 

The track representation for the given link

Parameters:
[in] link The Spotify link whose track you are interested in
Returns:
The track representation of the given track link If the link is not of track type then NULL is returned.
Examples:
browse.c.
sp_track* sp_link_as_track_and_offset ( sp_link link,
int *  offset 
)

The track and offset into track representation for the given link

Parameters:
[in] link The Spotify link whose track you are interested in
[out] offset Pointer to offset into track (in milliseconds). If the link does not contain an offset this will be set to 0.
Returns:
The track representation of the given track link If the link is not of track type then NULL is returned.
sp_user* sp_link_as_user ( sp_link link  ) 

The user representation for the given link

Parameters:
[in] link The Spotify link whose user you are interested in
Returns:
The user representation of the given link If the link is not of user type then NULL is returned
sp_link* sp_link_create_from_album ( sp_album album  ) 

Create a link object from an album

Parameters:
[in] album An album object
Returns:
A link representing the album
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
sp_link* sp_link_create_from_album_cover ( sp_album album,
sp_image_size  size 
)

Create an image link object from an album

Parameters:
[in] album An album object
[in] size The desired size of the image
Returns:
A link representing the album cover. Type is set to SP_LINKTYPE_IMAGE
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
sp_link* sp_link_create_from_artist ( sp_artist artist  ) 

Creates a link object from an artist

Parameters:
[in] artist An artist object
Returns:
A link object representing the artist
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
sp_link* sp_link_create_from_artist_portrait ( sp_artist artist,
sp_image_size  size 
)

Creates a link object pointing to an artist portrait

Parameters:
[in] artist Artist browse object
[in] size The desired size of the image
Returns:
A link object representing an image
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
sp_artistbrowse_num_portraits()
Examples:
toplist.c.
sp_link* sp_link_create_from_artistbrowse_portrait ( sp_artistbrowse arb,
int  index 
)

Creates a link object from an artist portrait

Parameters:
[in] arb Artist browse object
[in] index The index of the portrait. Should be in the interval [0, sp_artistbrowse_num_portraits() - 1]
Returns:
A link object representing an image
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
sp_artistbrowse_num_portraits()
Note:
The difference from sp_link_create_from_artist_portrait() is that the artist browse object may contain multiple portraits.
sp_link* sp_link_create_from_image ( sp_image image  ) 

Create a link object representing the given image

Parameters:
[in] image Image object
Returns:
A link representing the image.
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
sp_link* sp_link_create_from_playlist ( sp_playlist playlist  ) 

Create a link object representing the given playlist

Parameters:
[in] playlist Playlist object
Returns:
A link representing the playlist
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
Note:
Due to reasons in the playlist backend design and the Spotify URI scheme you need to wait for the playlist to be loaded before you can successfully construct an URI. If sp_link_create_from_playlist() returns NULL, try again after teh playlist_state_changed callback has fired.
sp_link* sp_link_create_from_search ( sp_search search  ) 

Generate a link object representing the current search

Parameters:
[in] search Search object
Returns:
A link representing the search
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
sp_link* sp_link_create_from_string ( const char *  link  ) 

Create a Spotify link given a string

Parameters:
[in] link A string representation of a Spotify link
Returns:
A link representation of the given string representation. If the link could not be parsed, this function returns NULL.
Note:
You need to release the link when you are done with it.
See also:
sp_link_type()
sp_link_release()
Examples:
browse.c.
sp_link* sp_link_create_from_track ( sp_track track,
int  offset 
)

Generates a link object from a track

Parameters:
[in] track A track object
[in] offset Offset in track in ms.
Returns:
A link representing the track
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
Examples:
browse.c.
sp_link* sp_link_create_from_user ( sp_user user  ) 

Create a link object representing the given playlist

Parameters:
[in] user User object
Returns:
A link representing the profile.
Note:
You need to release the link when you are done with it.
See also:
sp_link_release()
sp_error sp_link_release ( sp_link link  ) 

Decrease the reference count of a link

Parameters:
[in] link The link object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
browse.c, and toplist.c.
sp_linktype sp_link_type ( sp_link link  ) 

The link type of the specified link

Parameters:
[in] link The Spotify link whose type you are interested in
Returns:
The link type of the specified link - see the sp_linktype enum for possible values
Examples:
browse.c.

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__playlist.html ================================================ libspotify: Playlist subsystem

Playlist subsystem

Data Structures

struct  sp_playlist_callbacks
struct  sp_playlistcontainer_callbacks

Typedefs

typedef struct
sp_playlist_callbacks 
sp_playlist_callbacks
typedef struct
sp_playlistcontainer_callbacks 
sp_playlistcontainer_callbacks

Functions

bool sp_playlist_is_loaded (sp_playlist *playlist)
sp_error sp_playlist_add_callbacks (sp_playlist *playlist, sp_playlist_callbacks *callbacks, void *userdata)
sp_error sp_playlist_remove_callbacks (sp_playlist *playlist, sp_playlist_callbacks *callbacks, void *userdata)
int sp_playlist_num_tracks (sp_playlist *playlist)
sp_tracksp_playlist_track (sp_playlist *playlist, int index)
int sp_playlist_track_create_time (sp_playlist *playlist, int index)
sp_usersp_playlist_track_creator (sp_playlist *playlist, int index)
bool sp_playlist_track_seen (sp_playlist *playlist, int index)
sp_error sp_playlist_track_set_seen (sp_playlist *playlist, int index, bool seen)
const char * sp_playlist_track_message (sp_playlist *playlist, int index)
const char * sp_playlist_name (sp_playlist *playlist)
sp_error sp_playlist_rename (sp_playlist *playlist, const char *new_name)
sp_usersp_playlist_owner (sp_playlist *playlist)
bool sp_playlist_is_collaborative (sp_playlist *playlist)
sp_error sp_playlist_set_collaborative (sp_playlist *playlist, bool collaborative)
sp_error sp_playlist_set_autolink_tracks (sp_playlist *playlist, bool link)
const char * sp_playlist_get_description (sp_playlist *playlist)
bool sp_playlist_get_image (sp_playlist *playlist, byte image[20])
bool sp_playlist_has_pending_changes (sp_playlist *playlist)
sp_error sp_playlist_add_tracks (sp_playlist *playlist, sp_track *const *tracks, int num_tracks, int position, sp_session *session)
sp_error sp_playlist_remove_tracks (sp_playlist *playlist, const int *tracks, int num_tracks)
sp_error sp_playlist_reorder_tracks (sp_playlist *playlist, const int *tracks, int num_tracks, int new_position)
unsigned int sp_playlist_num_subscribers (sp_playlist *playlist)
sp_subscriberssp_playlist_subscribers (sp_playlist *playlist)
sp_error sp_playlist_subscribers_free (sp_subscribers *subscribers)
sp_error sp_playlist_update_subscribers (sp_session *session, sp_playlist *playlist)
bool sp_playlist_is_in_ram (sp_session *session, sp_playlist *playlist)
sp_error sp_playlist_set_in_ram (sp_session *session, sp_playlist *playlist, bool in_ram)
sp_playlistsp_playlist_create (sp_session *session, sp_link *link)
sp_error sp_playlist_set_offline_mode (sp_session *session, sp_playlist *playlist, bool offline)
sp_playlist_offline_status sp_playlist_get_offline_status (sp_session *session, sp_playlist *playlist)
int sp_playlist_get_offline_download_completed (sp_session *session, sp_playlist *playlist)
sp_error sp_playlist_add_ref (sp_playlist *playlist)
sp_error sp_playlist_release (sp_playlist *playlist)
sp_error sp_playlistcontainer_add_callbacks (sp_playlistcontainer *pc, sp_playlistcontainer_callbacks *callbacks, void *userdata)
sp_error sp_playlistcontainer_remove_callbacks (sp_playlistcontainer *pc, sp_playlistcontainer_callbacks *callbacks, void *userdata)
int sp_playlistcontainer_num_playlists (sp_playlistcontainer *pc)
bool sp_playlistcontainer_is_loaded (sp_playlistcontainer *pc)
sp_playlistsp_playlistcontainer_playlist (sp_playlistcontainer *pc, int index)
sp_playlist_type sp_playlistcontainer_playlist_type (sp_playlistcontainer *pc, int index)
sp_error sp_playlistcontainer_playlist_folder_name (sp_playlistcontainer *pc, int index, char *buffer, int buffer_size)
sp_uint64 sp_playlistcontainer_playlist_folder_id (sp_playlistcontainer *pc, int index)
sp_playlistsp_playlistcontainer_add_new_playlist (sp_playlistcontainer *pc, const char *name)
sp_playlistsp_playlistcontainer_add_playlist (sp_playlistcontainer *pc, sp_link *link)
sp_error sp_playlistcontainer_remove_playlist (sp_playlistcontainer *pc, int index)
sp_error sp_playlistcontainer_move_playlist (sp_playlistcontainer *pc, int index, int new_position, bool dry_run)
sp_error sp_playlistcontainer_add_folder (sp_playlistcontainer *pc, int index, const char *name)
sp_usersp_playlistcontainer_owner (sp_playlistcontainer *pc)
sp_error sp_playlistcontainer_add_ref (sp_playlistcontainer *pc)
sp_error sp_playlistcontainer_release (sp_playlistcontainer *pc)
int sp_playlistcontainer_get_unseen_tracks (sp_playlistcontainer *pc, sp_playlist *playlist, sp_track **tracks, int num_tracks)
int sp_playlistcontainer_clear_unseen_tracks (sp_playlistcontainer *pc, sp_playlist *playlist)

Detailed Description

The playlist subsystem handles playlists and playlist containers (list of playlists).

The playlist container functions are always valid, but your playlists are not guaranteed to be loaded until the sp_session_callbacks::logged_in callback has been issued.


Typedef Documentation

Playlist callbacks

Used to get notifications when playlists are updated. If some callbacks should not be of interest, set them to NULL.

Playlist container callbacks. If some callbacks should not be of interest, set them to NULL.

See also:
sp_playlistcontainer_add_callbacks
sp_playlistcontainer_remove_callbacks

Function Documentation

sp_error sp_playlist_add_callbacks ( sp_playlist playlist,
sp_playlist_callbacks callbacks,
void *  userdata 
)

Register interest in the given playlist

Here is a snippet from jukebox.c:

    sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);

Parameters:
[in] playlist Playlist object
[in] callbacks Callbacks, see sp_playlist_callbacks
[in] userdata Userdata to be passed to callbacks
See also:
sp_playlist_remove_callbacks
Examples:
browse.c, and jukebox.c.
sp_error sp_playlist_add_ref ( sp_playlist playlist  ) 

Increase the reference count of a playlist

Parameters:
[in] playlist The playlist object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_playlist_add_tracks ( sp_playlist playlist,
sp_track *const *  tracks,
int  num_tracks,
int  position,
sp_session session 
)

Add tracks to a playlist

Parameters:
[in] playlist Playlist object
[in] tracks Array of pointer to tracks.
[in] num_tracks Length of tracks array
[in] position Start position in playlist where to insert the tracks
[in] session Your session object
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INVALID_INDATA - position is > current playlist length SP_ERROR_PERMISSION_DENIED
sp_playlist* sp_playlist_create ( sp_session session,
sp_link link 
)

Load an already existing playlist without adding it to a playlistcontainer.

Parameters:
[in] session Session object
[in] link Link object referring to a playlist
Returns:
A playlist. The reference is owned by the caller and should be released with sp_playlist_release()
Examples:
browse.c.
const char* sp_playlist_get_description ( sp_playlist playlist  ) 

Get description for a playlist

Parameters:
[in] playlist Playlist object
Returns:
Playlist description or NULL if unset
bool sp_playlist_get_image ( sp_playlist playlist,
byte  image[20] 
)

Get description for a playlist

Parameters:
[in] playlist Playlist object
[out] image 20 byte image id
Returns:
TRUE if playlist has an image, FALSE if not
int sp_playlist_get_offline_download_completed ( sp_session session,
sp_playlist playlist 
)

Get download progress for an offline playlist

Parameters:
[in] session Session object
[in] playlist Playlist object
Returns:
Value from 0 - 100 that indicates amount of playlist that is downloaded or 0 if the playlist is not in the SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING mode.
See also:
sp_playlist_offline_status()
sp_playlist_offline_status sp_playlist_get_offline_status ( sp_session session,
sp_playlist playlist 
)

Get offline status for a playlist

Parameters:
[in] session Session object
[in] playlist Playlist object
Returns:
sp_playlist_offline_status
See also:
When in SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING mode the sp_playlist_get_offline_download_completed() method can be used to query progress of the download
bool sp_playlist_has_pending_changes ( sp_playlist playlist  ) 

Check if a playlist has pending changes

Pending changes are local changes that have not yet been acknowledged by the server.

Parameters:
[in] playlist Playlist object
Returns:
A flag representing if there are pending changes or not
bool sp_playlist_is_collaborative ( sp_playlist playlist  ) 

Return collaborative status for a playlist.

A playlist in collaborative state can be modifed by all users, not only the user owning the list

Parameters:
[in] playlist Playlist object
Returns:
true if playlist is collaborative, otherwise false
bool sp_playlist_is_in_ram ( sp_session session,
sp_playlist playlist 
)

Return whether a playlist is loaded in RAM (as opposed to only stored on disk)

Parameters:
[in] session Session object
[in] playlist Playlist object
Returns:
True iff playlist is in RAM, False otherwise
Note:
When a playlist is no longer in RAM it will appear empty. However, libspotify will retain information about the list metadata (owner, title, picture, etc) in RAM. There is one caveat tough: If libspotify has never seen the playlist before this metadata will also be unset. In order for libspotify to get the metadata the playlist needs to be loaded at least once. In order words, if libspotify starts with an empty playlist cache and the application has set 'initially_unload_playlists' config parameter to True all playlists will be empty. It will not be possible to generate URI's to the playlists nor extract playlist title until the application calls sp_playlist_set_in_ram(..., true). So an application that needs to stay within a low memory profile would need to cycle thru all new playlists in order to extract metadata.

The easiest way to detect this case is when sp_playlist_is_in_ram() returns false and sp_link_create_from_playlist() returns NULL

bool sp_playlist_is_loaded ( sp_playlist playlist  ) 

Get load status for the specified playlist. If it's false, you have to wait until playlist_state_changed happens, and check again if is_loaded has changed

Parameters:
[in] playlist Playlist object
Returns:
True if playlist is loaded, otherwise false
Examples:
browse.c.
const char* sp_playlist_name ( sp_playlist playlist  ) 

Return name of given playlist

Parameters:
[in] playlist Playlist object
Returns:
The name of the given playlist
Examples:
browse.c, and jukebox.c.
unsigned int sp_playlist_num_subscribers ( sp_playlist playlist  ) 

Return number of subscribers for a given playlist

Parameters:
[in] playlist Playlist object
Returns:
Number of subscribers
int sp_playlist_num_tracks ( sp_playlist playlist  ) 

Return number of tracks in the given playlist

Parameters:
[in] playlist Playlist object
Returns:
The number of tracks in the playlist
Examples:
browse.c, and jukebox.c.
sp_user* sp_playlist_owner ( sp_playlist playlist  ) 

Return a pointer to the user for the given playlist

Parameters:
[in] playlist Playlist object
Returns:
User object
sp_error sp_playlist_release ( sp_playlist playlist  ) 

Decrease the reference count of a playlist

Parameters:
[in] playlist The playlist object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
browse.c.
sp_error sp_playlist_remove_callbacks ( sp_playlist playlist,
sp_playlist_callbacks callbacks,
void *  userdata 
)

Unregister interest in the given playlist

The combination of (callbacks, userdata) is used to find the entry to be removed

Here is a snippet from jukebox.c:

    sp_playlist_remove_callbacks(pl, &pl_callbacks, NULL);

Parameters:
[in] playlist Playlist object
[in] callbacks Callbacks, see sp_playlist_callbacks
[in] userdata Userdata to be passed to callbacks
See also:
sp_playlist_add_callbacks
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
browse.c, and jukebox.c.
sp_error sp_playlist_remove_tracks ( sp_playlist playlist,
const int *  tracks,
int  num_tracks 
)

Remove tracks from a playlist

Parameters:
[in] playlist Playlist object
[in] tracks Array of pointer to track indices. A certain track index should be present at most once, e.g. [0, 1, 2] is valid indata, whereas [0, 1, 1] is invalid.
[in] num_tracks Length of tracks array
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_PERMISSION_DENIED
Examples:
jukebox.c.
sp_error sp_playlist_rename ( sp_playlist playlist,
const char *  new_name 
)

Rename the given playlist The name must not consist of only spaces and it must be shorter than 256 characters.

Parameters:
[in] playlist Playlist object
[in] new_name New name for playlist
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INVALID_INDATA SP_ERROR_PERMISSION_DENIED
sp_error sp_playlist_reorder_tracks ( sp_playlist playlist,
const int *  tracks,
int  num_tracks,
int  new_position 
)

Move tracks in playlist

Parameters:
[in] playlist Playlist object
[in] tracks Array of pointer to track indices to be moved. A certain track index should be present at most once, e.g. [0, 1, 2] is valid indata, whereas [0, 1, 1] is invalid.
[in] num_tracks Length of tracks array
[in] new_position New position for tracks
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INVALID_INDATA - position is > current playlist length SP_ERROR_PERMISSION_DENIED
sp_error sp_playlist_set_autolink_tracks ( sp_playlist playlist,
bool  link 
)

Set autolinking state for a playlist.

If a playlist is autolinked, unplayable tracks will be made playable by linking them to other Spotify tracks, where possible.

Parameters:
[in] playlist Playlist object
[in] link True or false
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_playlist_set_collaborative ( sp_playlist playlist,
bool  collaborative 
)

Set collaborative status for a playlist.

A playlist in collaborative state can be modified by all users, not only the user owning the list

Parameters:
[in] playlist Playlist object
[in] collaborative True or false
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_playlist_set_in_ram ( sp_session session,
sp_playlist playlist,
bool  in_ram 
)

Return whether a playlist is loaded in RAM (as opposed to only stored on disk)

Parameters:
[in] session Session object
[in] playlist Playlist object
[in] in_ram Controls whether or not to keep the list in RAM
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_playlist_set_offline_mode ( sp_session session,
sp_playlist playlist,
bool  offline 
)

Mark a playlist to be synchronized for offline playback. The playlist must be in the users playlistcontainer

Parameters:
[in] session Session object
[in] playlist Playlist object
[in] offline True iff playlist should be offline, false otherwise
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_subscribers* sp_playlist_subscribers ( sp_playlist playlist  ) 

Return subscribers for a playlist

Parameters:
[in] playlist Playlist object
Returns:
sp_subscribers struct with array of canonical usernames. This object should be free'd using sp_playlist_subscribers_free()
Note:
The count returned for this function may be less than those returned by sp_playlist_num_subscribers(). Spotify does not track each user subscribed to a playlist for playlist with many (>500) subscribers.
sp_error sp_playlist_subscribers_free ( sp_subscribers subscribers  ) 

Free object returned from sp_playlist_subscribers()

Parameters:
[in] subscribers Subscribers object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_track* sp_playlist_track ( sp_playlist playlist,
int  index 
)

Return the track at the given index in a playlist

Parameters:
[in] playlist Playlist object
[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1]
Returns:
The track at the given index
Examples:
browse.c, and jukebox.c.
int sp_playlist_track_create_time ( sp_playlist playlist,
int  index 
)

Return when the given index was added to the playlist

Parameters:
[in] playlist Playlist object
[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1]
Returns:
Time, Seconds since unix epoch.
sp_user* sp_playlist_track_creator ( sp_playlist playlist,
int  index 
)

Return user that added the given index in the playlist

Parameters:
[in] playlist Playlist object
[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1]
Returns:
User object
const char* sp_playlist_track_message ( sp_playlist playlist,
int  index 
)

Return a message attached to a playlist item. Typically used on inbox.

Parameters:
[in] playlist Playlist object
[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1]
Returns:
UTF-8 encoded message, or NULL if no message is present
bool sp_playlist_track_seen ( sp_playlist playlist,
int  index 
)

Return if a playlist entry is marked as seen or not

Parameters:
[in] playlist Playlist object
[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1]
Returns:
Seen state
sp_error sp_playlist_track_set_seen ( sp_playlist playlist,
int  index,
bool  seen 
)

Set seen status of a playlist entry

Parameters:
[in] playlist Playlist object
[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1]
[in] seen Seen status to be set
Returns:
error One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INDEX_OUT_OF_RANGE
sp_error sp_playlist_update_subscribers ( sp_session session,
sp_playlist playlist 
)

Ask library to update the subscription count for a playlist

When the subscription info has been fetched from the Spotify backend the playlist subscribers_changed() callback will be invoked. In that callback use sp_playlist_num_subscribers() and/or sp_playlist_subscribers() to get information about the subscribers. You can call those two functions anytime you want but the information might not be up to date in such cases

Parameters:
[in] session Session object
[in] playlist Playlist object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_playlistcontainer_add_callbacks ( sp_playlistcontainer pc,
sp_playlistcontainer_callbacks callbacks,
void *  userdata 
)

Register interest in changes to a playlist container

Parameters:
[in] pc Playlist container
[in] callbacks Callbacks, see sp_playlistcontainer_callbacks
[in] userdata Opaque value passed to callbacks.
Note:
Every sp_playlistcontainer_add_callbacks() needs to be paired with a corresponding sp_playlistcontainer_remove_callbacks() that is invoked before releasing the last reference you own for the container. In other words, you must make sure to have removed all the callbacks before the container gets destroyed.
See also:
sp_session_playlistcontainer()
sp_playlistcontainer_remove_callbacks
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
jukebox.c.
sp_error sp_playlistcontainer_add_folder ( sp_playlistcontainer pc,
int  index,
const char *  name 
)

Add a playlist folder

Parameters:
[in] pc Playlist container
[in] index Position of SP_PLAYLIST_TYPE_START_FOLDER entry
[in] name Name of group
Returns:
error One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INDEX_OUT_OF_RANGE
Note:
This operation will actually create two playlists. One of type SP_PLAYLIST_TYPE_START_FOLDER and immediately following a SP_PLAYLIST_TYPE_END_FOLDER one.

To remove a playlist folder both of these must be deleted or the list will be left in an inconsistant state.

There is no way to rename a playlist folder. Instead you need to remove the folder and recreate it again.

sp_playlist* sp_playlistcontainer_add_new_playlist ( sp_playlistcontainer pc,
const char *  name 
)

Add an empty playlist at the end of the playlist container. The name must not consist of only spaces and it must be shorter than 256 characters.

Parameters:
[in] pc Playlist container
[in] name Name of new playlist
Returns:
Pointer to the new playlist. Can be NULL if the operation fails.
sp_playlist* sp_playlistcontainer_add_playlist ( sp_playlistcontainer pc,
sp_link link 
)

Add an existing playlist at the end of the given playlist container

Parameters:
[in] pc Playlist container
[in] link Link object pointing to a playlist
Returns:
Pointer to the new playlist. Will be NULL if the playlist already exists.
sp_error sp_playlistcontainer_add_ref ( sp_playlistcontainer pc  ) 

Increase reference count on playlistconatiner object

Parameters:
[in] pc Playlist container.
Returns:
One of the following errors, from sp_error SP_ERROR_OK
int sp_playlistcontainer_clear_unseen_tracks ( sp_playlistcontainer pc,
sp_playlist playlist 
)

Clears a playlist from unseen tracks, so that next call to sp_playlistcontainer_get_unseen_tracks() will return 0 until a new track is added to the playslist.

Parameters:
[in] pc Playlist container.
[in] playlist Playlist object.
Returns:
Returns 0 on success and -1 on failure.
int sp_playlistcontainer_get_unseen_tracks ( sp_playlistcontainer pc,
sp_playlist playlist,
sp_track **  tracks,
int  num_tracks 
)

Get the number of new tracks in a playlist since the corresponding function sp_playlistcontainer_clear_unseen_tracks() was called. The function always returns the number of new tracks, and fills the tracks array with the new tracks, but not more than specified in num_tracks. The function will return a negative value on failure.

Parameters:
[in] pc Playlist container.
[in] playlist Playlist object.
[out] tracks Array of pointer to new tracks (maybe NULL)
[in] num_tracks Size of tracks array
Returns:
Returns the number of unseen tracks
bool sp_playlistcontainer_is_loaded ( sp_playlistcontainer pc  ) 

Return true if the playlistcontainer is fully loaded

Parameters:
[in] pc Playlist container
Returns:
True if container is loaded
Note:
The container_loaded callback will be invoked when this flips to true
sp_error sp_playlistcontainer_move_playlist ( sp_playlistcontainer pc,
int  index,
int  new_position,
bool  dry_run 
)

Move a playlist in the playlist container

Parameters:
[in] pc Playlist container
[in] index Index of playlist to be moved
[in] new_position New position for the playlist
[in] dry_run Do not execute the move, only check if it possible
Returns:
error One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INDEX_OUT_OF_RANGE SP_ERROR_INVALID_INDATA - If trying to move a folder into itself
int sp_playlistcontainer_num_playlists ( sp_playlistcontainer pc  ) 

Return the number of playlists in the given playlist container

Parameters:
[in] pc Playlist container
Returns:
Number of playlists, -1 if undefined
See also:
sp_session_playlistcontainer()
Examples:
jukebox.c.
sp_user* sp_playlistcontainer_owner ( sp_playlistcontainer pc  ) 

Return a pointer to the user object of the owner.

Parameters:
[in] pc Playlist container.
Returns:
The user object or NULL if unknown or none.
sp_playlist* sp_playlistcontainer_playlist ( sp_playlistcontainer pc,
int  index 
)

Return a pointer to the playlist at a specific index

Parameters:
[in] pc Playlist container
[in] index Index in playlist container. Should be in the interval [0, sp_playlistcontainer_num_playlists() - 1]
Returns:
The playlist object
See also:
sp_session_playlistcontainer()
Examples:
jukebox.c.
sp_uint64 sp_playlistcontainer_playlist_folder_id ( sp_playlistcontainer pc,
int  index 
)

Return the folder id at index

Parameters:
[in] pc Playlist container
[in] index Index in playlist container. Should be in the interval [0, sp_playlistcontainer_num_playlists() - 1]
Returns:
The group ID of the folder. Returns 0 on index out of range, pc being NULL or indexed item not being a folder
See also:
sp_session_playlistcontainer()
sp_error sp_playlistcontainer_playlist_folder_name ( sp_playlistcontainer pc,
int  index,
char *  buffer,
int  buffer_size 
)

Return the folder name at index

Parameters:
[in] pc Playlist container
[in] index Index in playlist container. Should be in the interval [0, sp_playlistcontainer_num_playlists() - 1]. Index should point at a start-folder entry, otherwise the empty string is written to buffer.
[in] buffer Pointer to char[] where to store folder name
[in] buffer_size Size of array
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INDEX_OUT_OF_RANGE
See also:
sp_session_playlistcontainer()
sp_playlist_type sp_playlistcontainer_playlist_type ( sp_playlistcontainer pc,
int  index 
)

Return the type of the playlist at a index

Parameters:
[in] pc Playlist container
[in] index Index in playlist container. Should be in the interval [0, sp_playlistcontainer_num_playlists() - 1]
Returns:
Type of the playlist,
See also:
sp_playlist_type
sp_session_playlistcontainer()
sp_error sp_playlistcontainer_release ( sp_playlistcontainer pc  ) 

Release reference count on playlistconatiner object

Parameters:
[in] pc Playlist container.
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_playlistcontainer_remove_callbacks ( sp_playlistcontainer pc,
sp_playlistcontainer_callbacks callbacks,
void *  userdata 
)

Unregister interest in changes to a playlist container

Parameters:
[in] pc Playlist container
[in] callbacks Callbacks, see sp_playlistcontainer_callbacks
[in] userdata Opaque value passed to callbacks.
See also:
sp_session_playlistcontainer()
sp_playlistcontainer_add_callbacks
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_playlistcontainer_remove_playlist ( sp_playlistcontainer pc,
int  index 
)

Remove playlist at index from the given playlist container

Parameters:
[in] pc Playlist container
[in] index Index of playlist to be removed
Returns:
error One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INDEX_OUT_OF_RANGE

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__search.html ================================================ libspotify: Search subsystem

Search subsystem

Typedefs

typedef void search_complete_cb (sp_search *result, void *userdata)

Functions

sp_searchsp_search_create (sp_session *session, const char *query, int track_offset, int track_count, int album_offset, int album_count, int artist_offset, int artist_count, int playlist_offset, int playlist_count, sp_search_type search_type, search_complete_cb *callback, void *userdata)
bool sp_search_is_loaded (sp_search *search)
sp_error sp_search_error (sp_search *search)
int sp_search_num_tracks (sp_search *search)
sp_tracksp_search_track (sp_search *search, int index)
int sp_search_num_albums (sp_search *search)
sp_albumsp_search_album (sp_search *search, int index)
int sp_search_num_playlists (sp_search *search)
sp_playlistsp_search_playlist (sp_search *search, int index)
const char * sp_search_playlist_name (sp_search *search, int index)
const char * sp_search_playlist_uri (sp_search *search, int index)
const char * sp_search_playlist_image_uri (sp_search *search, int index)
int sp_search_num_artists (sp_search *search)
sp_artistsp_search_artist (sp_search *search, int index)
const char * sp_search_query (sp_search *search)
const char * sp_search_did_you_mean (sp_search *search)
int sp_search_total_tracks (sp_search *search)
int sp_search_total_albums (sp_search *search)
int sp_search_total_artists (sp_search *search)
int sp_search_total_playlists (sp_search *search)
sp_error sp_search_add_ref (sp_search *search)
sp_error sp_search_release (sp_search *search)

Typedef Documentation

typedef void search_complete_cb(sp_search *result, void *userdata)

The type of a callback used in sp_search_create()

When this callback is called, the sp_track_is_loaded(), sp_album_is_loaded(), and sp_artist_is_loaded() functions will return non-zero for the objects contained in the search result.

Parameters:
[in] result The same pointer returned by sp_search_create()
[in] userdata The opaque pointer given to sp_search_create()

Function Documentation

sp_error sp_search_add_ref ( sp_search search  ) 

Increase the reference count of a search result

Parameters:
[in] search The search result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_album* sp_search_album ( sp_search search,
int  index 
)

Return the album at the given index in the given search object

Parameters:
[in] search Search object
[in] index Index of the wanted album. Should be in the interval [0, sp_search_num_albums() - 1]
Returns:
The album at the given index in the given search object
Examples:
search.c.
sp_artist* sp_search_artist ( sp_search search,
int  index 
)

Return the artist at the given index in the given search object

Parameters:
[in] search Search object
[in] index Index of the wanted artist. Should be in the interval [0, sp_search_num_artists() - 1]
Returns:
The artist at the given index in the given search object
Examples:
search.c.
sp_search* sp_search_create ( sp_session session,
const char *  query,
int  track_offset,
int  track_count,
int  album_offset,
int  album_count,
int  artist_offset,
int  artist_count,
int  playlist_offset,
int  playlist_count,
sp_search_type  search_type,
search_complete_cb callback,
void *  userdata 
)

Create a search object from the given query

Parameters:
[in] session Session
[in] query Query search string, e.g. 'The Rolling Stones' or 'album:"The Black Album"'
[in] track_offset The offset among the tracks of the result
[in] track_count The number of tracks to ask for
[in] album_offset The offset among the albums of the result
[in] album_count The number of albums to ask for
[in] artist_offset The offset among the artists of the result
[in] artist_count The number of artists to ask for
[in] playlist_offset The offset among the playlists of the result
[in] playlist_count The number of playlists to ask for
[in] search_type Type of search, can be used for suggest searches
[in] callback Callback that will be called once the search operation is complete. Pass NULL if you are not interested in this event.
[in] userdata Opaque pointer passed to callback
Returns:
Pointer to a search object. To free the object, use sp_search_release()
Examples:
search.c.
const char* sp_search_did_you_mean ( sp_search search  ) 

Return the "Did you mean" query for the given search object

Parameters:
[in] search Search object
Returns:
The "Did you mean" query for the given search object, or the empty string if no such info is available
Examples:
search.c.
sp_error sp_search_error ( sp_search search  ) 

Check if search returned an error code.

Parameters:
[in] search Search object
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_IS_LOADING SP_ERROR_OTHER_PERMANENT SP_ERROR_OTHER_TRANSIENT
Examples:
search.c.
bool sp_search_is_loaded ( sp_search search  ) 

Get load status for the specified search. Before it is loaded, it will behave as an empty search result.

Parameters:
[in] search Search object
Returns:
True if search is loaded, otherwise false
int sp_search_num_albums ( sp_search search  ) 

Get the number of albums for the specified search

Parameters:
[in] search Search object
Returns:
The number of albums for the specified search
Examples:
search.c.
int sp_search_num_artists ( sp_search search  ) 

Get the number of artists for the specified search

Parameters:
[in] search Search object
Returns:
The number of artists for the specified search
Examples:
search.c.
int sp_search_num_playlists ( sp_search search  ) 

Get the number of playlists for the specified search

Parameters:
[in] search Search object
Returns:
The number of playlists for the specified search
Examples:
search.c.
int sp_search_num_tracks ( sp_search search  ) 

Get the number of tracks for the specified search

Parameters:
[in] search Search object
Returns:
The number of tracks for the specified search
Examples:
search.c.
sp_playlist* sp_search_playlist ( sp_search search,
int  index 
)

Load the playlist at the given index in the given search object

Parameters:
[in] search Search object
[in] index Index of the wanted playlist. Should be in the interval [0, sp_search_num_playlists() - 1]
Returns:
A playlist object. This reference is owned by the caller and should be released with sp_playlist_release()
const char* sp_search_playlist_image_uri ( sp_search search,
int  index 
)

Return the image_uri of a playlist at the given index in the given search object

Parameters:
[in] search Search object
[in] index Index of the wanted playlist. Should be in the interval [0, sp_search_num_playlists() - 1]
Returns:
The playlist image_uri at the given index in the given search object
const char* sp_search_playlist_name ( sp_search search,
int  index 
)

Return the playlist at the given index in the given search object

Parameters:
[in] search Search object
[in] index Index of the wanted playlist. Should be in the interval [0, sp_search_num_playlists() - 1]
Returns:
The playlist name at the given index in the given search object
Examples:
search.c.
const char* sp_search_playlist_uri ( sp_search search,
int  index 
)

Return the uri of a playlist at the given index in the given search object

Parameters:
[in] search Search object
[in] index Index of the wanted playlist. Should be in the interval [0, sp_search_num_playlists() - 1]
Returns:
The playlist uri at the given index in the given search object
const char* sp_search_query ( sp_search search  ) 

Return the search query for the given search object

Parameters:
[in] search Search object
Returns:
The search query for the given search object
Examples:
search.c.
sp_error sp_search_release ( sp_search search  ) 

Decrease the reference count of a search result

Parameters:
[in] search The search result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
search.c.
int sp_search_total_albums ( sp_search search  ) 

Return the total number of albums for the search query - regardless of the interval requested at creation. If this value is larger than the interval specified at creation of the search object, more search results are available. To fetch these, create a new search object with a new interval.

Parameters:
[in] search Search object
Returns:
The total number of albums matching the original query
int sp_search_total_artists ( sp_search search  ) 

Return the total number of artists for the search query - regardless of the interval requested at creation. If this value is larger than the interval specified at creation of the search object, more search results are available. To fetch these, create a new search object with a new interval.

Parameters:
[in] search Search object
Returns:
The total number of artists matching the original query
int sp_search_total_playlists ( sp_search search  ) 

Return the total number of playlists for the search query - regardless of the interval requested at creation. If this value is larger than the interval specified at creation of the search object, more search results are available. To fetch these, create a new search object with a new interval.

Parameters:
[in] search Search object
Returns:
The total number of playlists matching the original query
int sp_search_total_tracks ( sp_search search  ) 

Return the total number of tracks for the search query - regardless of the interval requested at creation. If this value is larger than the interval specified at creation of the search object, more search results are available. To fetch these, create a new search object with a new interval.

Parameters:
[in] search Search object
Returns:
The total number of tracks matching the original query
Examples:
search.c.
sp_track* sp_search_track ( sp_search search,
int  index 
)

Return the track at the given index in the given search object

Parameters:
[in] search Search object
[in] index Index of the wanted track. Should be in the interval [0, sp_search_num_tracks() - 1]
Returns:
The track at the given index in the given search object
Examples:
search.c.

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__session.html ================================================ libspotify: Session handling

Data Structures

struct  sp_audioformat
struct  sp_audio_buffer_stats
struct  sp_subscribers
struct  sp_offline_sync_status
struct  sp_session_callbacks
struct  sp_session_config

Defines

#define SPOTIFY_API_VERSION   12

Typedefs

typedef enum sp_connectionstate sp_connectionstate
typedef enum sp_sampletype sp_sampletype
typedef struct sp_audioformat sp_audioformat
typedef enum sp_bitrate sp_bitrate
typedef enum sp_playlist_type sp_playlist_type
typedef enum sp_search_type sp_search_type
typedef enum
sp_playlist_offline_status 
sp_playlist_offline_status
typedef enum sp_availability sp_track_availability
typedef enum
sp_track_offline_status 
sp_track_offline_status
typedef enum sp_image_size sp_image_size
typedef struct
sp_audio_buffer_stats 
sp_audio_buffer_stats
typedef struct sp_subscribers sp_subscribers
typedef enum sp_connection_type sp_connection_type
typedef enum sp_connection_rules sp_connection_rules
typedef enum sp_artistbrowse_type sp_artistbrowse_type
typedef struct
sp_offline_sync_status 
sp_offline_sync_status
typedef struct sp_session_callbacks sp_session_callbacks
typedef struct sp_session_config sp_session_config

Enumerations

enum  sp_connectionstate {
  SP_CONNECTION_STATE_LOGGED_OUT = 0,
  SP_CONNECTION_STATE_LOGGED_IN = 1,
  SP_CONNECTION_STATE_DISCONNECTED = 2,
  SP_CONNECTION_STATE_UNDEFINED = 3,
  SP_CONNECTION_STATE_OFFLINE = 4
}
enum  sp_sampletype { SP_SAMPLETYPE_INT16_NATIVE_ENDIAN = 0 }
enum  sp_bitrate {
  SP_BITRATE_160k = 0,
  SP_BITRATE_320k = 1,
  SP_BITRATE_96k = 2
}
enum  sp_playlist_type {
  SP_PLAYLIST_TYPE_PLAYLIST = 0,
  SP_PLAYLIST_TYPE_START_FOLDER = 1,
  SP_PLAYLIST_TYPE_END_FOLDER = 2,
  SP_PLAYLIST_TYPE_PLACEHOLDER = 3
}
enum  sp_search_type
enum  sp_playlist_offline_status {
  SP_PLAYLIST_OFFLINE_STATUS_NO = 0,
  SP_PLAYLIST_OFFLINE_STATUS_YES = 1,
  SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING = 2,
  SP_PLAYLIST_OFFLINE_STATUS_WAITING = 3
}
enum  sp_availability {
  SP_TRACK_AVAILABILITY_UNAVAILABLE = 0,
  SP_TRACK_AVAILABILITY_AVAILABLE = 1,
  SP_TRACK_AVAILABILITY_NOT_STREAMABLE = 2,
  SP_TRACK_AVAILABILITY_BANNED_BY_ARTIST = 3
}
enum  sp_track_offline_status {
  SP_TRACK_OFFLINE_NO = 0,
  SP_TRACK_OFFLINE_WAITING = 1,
  SP_TRACK_OFFLINE_DOWNLOADING = 2,
  SP_TRACK_OFFLINE_DONE = 3,
  SP_TRACK_OFFLINE_ERROR = 4,
  SP_TRACK_OFFLINE_DONE_EXPIRED = 5,
  SP_TRACK_OFFLINE_LIMIT_EXCEEDED = 6,
  SP_TRACK_OFFLINE_DONE_RESYNC = 7
}
enum  sp_image_size {
  SP_IMAGE_SIZE_NORMAL = 0,
  SP_IMAGE_SIZE_SMALL = 1,
  SP_IMAGE_SIZE_LARGE = 2
}
enum  sp_connection_type {
  SP_CONNECTION_TYPE_UNKNOWN = 0,
  SP_CONNECTION_TYPE_NONE = 1,
  SP_CONNECTION_TYPE_MOBILE = 2,
  SP_CONNECTION_TYPE_MOBILE_ROAMING = 3,
  SP_CONNECTION_TYPE_WIFI = 4,
  SP_CONNECTION_TYPE_WIRED = 5
}
enum  sp_connection_rules {
  SP_CONNECTION_RULE_NETWORK = 0x1,
  SP_CONNECTION_RULE_NETWORK_IF_ROAMING = 0x2,
  SP_CONNECTION_RULE_ALLOW_SYNC_OVER_MOBILE = 0x4,
  SP_CONNECTION_RULE_ALLOW_SYNC_OVER_WIFI = 0x8
}
enum  sp_artistbrowse_type {
  SP_ARTISTBROWSE_FULL,
  SP_ARTISTBROWSE_NO_TRACKS,
  SP_ARTISTBROWSE_NO_ALBUMS
}

Functions

sp_error sp_session_create (const sp_session_config *config, sp_session **sess)
sp_error sp_session_release (sp_session *sess)
sp_error sp_session_login (sp_session *session, const char *username, const char *password, bool remember_me, const char *blob)
sp_error sp_session_relogin (sp_session *session)
int sp_session_remembered_user (sp_session *session, char *buffer, size_t buffer_size)
const char * sp_session_user_name (sp_session *session)
sp_error sp_session_forget_me (sp_session *session)
sp_usersp_session_user (sp_session *session)
sp_error sp_session_logout (sp_session *session)
sp_error sp_session_flush_caches (sp_session *session)
sp_connectionstate sp_session_connectionstate (sp_session *session)
void * sp_session_userdata (sp_session *session)
sp_error sp_session_set_cache_size (sp_session *session, size_t size)
sp_error sp_session_process_events (sp_session *session, int *next_timeout)
sp_error sp_session_player_load (sp_session *session, sp_track *track)
sp_error sp_session_player_seek (sp_session *session, int offset)
sp_error sp_session_player_play (sp_session *session, bool play)
sp_error sp_session_player_unload (sp_session *session)
sp_error sp_session_player_prefetch (sp_session *session, sp_track *track)
sp_playlistcontainersp_session_playlistcontainer (sp_session *session)
sp_playlistsp_session_inbox_create (sp_session *session)
sp_playlistsp_session_starred_create (sp_session *session)
sp_playlistsp_session_starred_for_user_create (sp_session *session, const char *canonical_username)
sp_playlistcontainersp_session_publishedcontainer_for_user_create (sp_session *session, const char *canonical_username)
sp_error sp_session_preferred_bitrate (sp_session *session, sp_bitrate bitrate)
sp_error sp_session_preferred_offline_bitrate (sp_session *session, sp_bitrate bitrate, bool allow_resync)
bool sp_session_get_volume_normalization (sp_session *session)
sp_error sp_session_set_volume_normalization (sp_session *session, bool on)
sp_error sp_session_set_private_session (sp_session *session, bool enabled)
bool sp_session_is_private_session (sp_session *session)
sp_error sp_session_set_scrobbling (sp_session *session, sp_social_provider provider, sp_scrobbling_state state)
sp_error sp_session_is_scrobbling (sp_session *session, sp_social_provider provider, sp_scrobbling_state *state)
sp_error sp_session_is_scrobbling_possible (sp_session *session, sp_social_provider provider, bool *out)
sp_error sp_session_set_social_credentials (sp_session *session, sp_social_provider provider, const char *username, const char *password)
sp_error sp_session_set_connection_type (sp_session *session, sp_connection_type type)
sp_error sp_session_set_connection_rules (sp_session *session, sp_connection_rules rules)
int sp_offline_tracks_to_sync (sp_session *session)
int sp_offline_num_playlists (sp_session *session)
bool sp_offline_sync_get_status (sp_session *session, sp_offline_sync_status *status)
int sp_offline_time_left (sp_session *session)
int sp_session_user_country (sp_session *session)

Detailed Description

The concept of a session is fundamental for all communication with the Spotify ecosystem - it is the object responsible for communicating with the Spotify service. You will need to instantiate a session that then can be used to request artist information, perform searches etc.


Define Documentation

#define SPOTIFY_API_VERSION   12

Current version of the application interface, that is, the API described by this file.

This value should be set in the sp_session_config struct passed to sp_session_create().

If an (upgraded) library is no longer compatible with this version the error SP_ERROR_BAD_API_VERSION will be returned from sp_session_create(). Future versions of the library will provide you with some kind of mechanism to request an updated version of the library.


Typedef Documentation

Controls the type of data that will be included in artist browse queries

Buffer stats used by get_audio_buffer_stats callback

Audio format descriptor

typedef enum sp_bitrate sp_bitrate

Bitrate definitions for music streaming

Connection rules, bitwise OR of flags

The default is SP_CONNECTION_RULE_NETWORK | SP_CONNECTION_RULE_ALLOW_SYNC

Current connection type set using sp_session_set_connection_type()

Describes the current state of the connection

Image size

Offline sync status

Playlist offline status

Playlist types

Sample type descriptor

Search types

Session callbacks

Registered when you create a session. If some callbacks should not be of interest, set them to NULL.

Session config

List of subscribers returned by sp_playlist_subscribers()

Track availability

Track offline status


Enumeration Type Documentation

Controls the type of data that will be included in artist browse queries

Enumerator:
SP_ARTISTBROWSE_FULL 

All information except tophit tracks This mode is deprecated and will removed in a future release

SP_ARTISTBROWSE_NO_TRACKS 

Only albums and data about them, no tracks. In other words, sp_artistbrowse_num_tracks() will return 0

SP_ARTISTBROWSE_NO_ALBUMS 

Only return data about the artist (artist name, similar artist biography, etc No tracks or album will be abailable. sp_artistbrowse_num_tracks() and sp_artistbrowse_num_albums() will both return 0

Track availability

Enumerator:
SP_TRACK_AVAILABILITY_UNAVAILABLE 

Track is not available.

SP_TRACK_AVAILABILITY_AVAILABLE 

Track is available and can be played.

SP_TRACK_AVAILABILITY_NOT_STREAMABLE 

Track can not be streamed using this account.

SP_TRACK_AVAILABILITY_BANNED_BY_ARTIST 

Track not available on artist's reqeust.

enum sp_bitrate

Bitrate definitions for music streaming

Enumerator:
SP_BITRATE_160k 

Bitrate 160kbps.

SP_BITRATE_320k 

Bitrate 320kbps.

SP_BITRATE_96k 

Bitrate 96kbps.

Connection rules, bitwise OR of flags

The default is SP_CONNECTION_RULE_NETWORK | SP_CONNECTION_RULE_ALLOW_SYNC

Enumerator:
SP_CONNECTION_RULE_NETWORK 

Allow network traffic. When not set libspotify will force itself into offline mode.

SP_CONNECTION_RULE_NETWORK_IF_ROAMING 

Allow network traffic even if roaming.

SP_CONNECTION_RULE_ALLOW_SYNC_OVER_MOBILE 

Set to allow syncing of offline content over mobile connections.

SP_CONNECTION_RULE_ALLOW_SYNC_OVER_WIFI 

Set to allow syncing of offline content over WiFi.

Current connection type set using sp_session_set_connection_type()

Enumerator:
SP_CONNECTION_TYPE_UNKNOWN 

Connection type unknown (Default).

SP_CONNECTION_TYPE_NONE 

No connection.

SP_CONNECTION_TYPE_MOBILE 

Mobile data (EDGE, 3G, etc).

SP_CONNECTION_TYPE_MOBILE_ROAMING 

Roamed mobile data (EDGE, 3G, etc).

SP_CONNECTION_TYPE_WIFI 

Wireless connection.

SP_CONNECTION_TYPE_WIRED 

Ethernet cable, etc.

Describes the current state of the connection

Enumerator:
SP_CONNECTION_STATE_LOGGED_OUT 

User not yet logged in.

SP_CONNECTION_STATE_LOGGED_IN 

Logged in against a Spotify access point.

SP_CONNECTION_STATE_DISCONNECTED 

Was logged in, but has now been disconnected.

SP_CONNECTION_STATE_UNDEFINED 

The connection state is undefined.

SP_CONNECTION_STATE_OFFLINE 

Logged in in offline mode.

Image size

Enumerator:
SP_IMAGE_SIZE_NORMAL 

Normal image size.

SP_IMAGE_SIZE_SMALL 

Small image size.

SP_IMAGE_SIZE_LARGE 

Large image size.

Playlist offline status

Enumerator:
SP_PLAYLIST_OFFLINE_STATUS_NO 

Playlist is not offline enabled.

SP_PLAYLIST_OFFLINE_STATUS_YES 

Playlist is synchronized to local storage.

SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING 

This playlist is currently downloading. Only one playlist can be in this state any given time.

SP_PLAYLIST_OFFLINE_STATUS_WAITING 

Playlist is queued for download.

Playlist types

Enumerator:
SP_PLAYLIST_TYPE_PLAYLIST 

A normal playlist.

SP_PLAYLIST_TYPE_START_FOLDER 

Marks a folder starting point,.

SP_PLAYLIST_TYPE_END_FOLDER 

and ending point.

SP_PLAYLIST_TYPE_PLACEHOLDER 

Unknown entry.

Sample type descriptor

Enumerator:
SP_SAMPLETYPE_INT16_NATIVE_ENDIAN 

16-bit signed integer samples

Search types

Track offline status

Enumerator:
SP_TRACK_OFFLINE_NO 

Not marked for offline.

SP_TRACK_OFFLINE_WAITING 

Waiting for download.

SP_TRACK_OFFLINE_DOWNLOADING 

Currently downloading.

SP_TRACK_OFFLINE_DONE 

Downloaded OK and can be played.

SP_TRACK_OFFLINE_ERROR 

Error during download.

SP_TRACK_OFFLINE_DONE_EXPIRED 

Downloaded OK but not playable due to expiery.

SP_TRACK_OFFLINE_LIMIT_EXCEEDED 

Waiting because device have reached max number of allowed tracks.

SP_TRACK_OFFLINE_DONE_RESYNC 

Downloaded OK and available but scheduled for re-download.


Function Documentation

int sp_offline_num_playlists ( sp_session session  ) 

Return number of playlisys that is marked for offline synchronization

Parameters:
[in] session Session object
Returns:
Number of playlists
bool sp_offline_sync_get_status ( sp_session session,
sp_offline_sync_status status 
)

Return offline synchronization status. When the internal status is updated the offline_status_updated() callback will be invoked.

Parameters:
[in] session Session object
[out] status Status object that will be filled with info
Returns:
false if no synching is in progress (in which case the contents of status is undefined)
int sp_offline_time_left ( sp_session session  ) 

Return remaining time (in seconds) until the offline key store expires and the user is required to relogin

Parameters:
[in] session Session object
Returns:
Seconds until expiration
int sp_offline_tracks_to_sync ( sp_session session  ) 

Get total number of tracks that needs download before everything from all playlists that is marked for offline is fully synchronized

Parameters:
[in] session Session object
Returns:
Number of tracks
sp_connectionstate sp_session_connectionstate ( sp_session session  ) 

The connection state of the specified session.

Parameters:
[in] session Your session object
Returns:
The connection state - see the sp_connectionstate enum for possible values
sp_error sp_session_create ( const sp_session_config config,
sp_session **  sess 
)

Initialize a session. The session returned will be initialized, but you will need to log in before you can perform any other operation Currently it is not supported to have multiple active sessions, and it's recommended to only call this once per process.

Here is a snippet from spshell.c:

    config.api_version = SPOTIFY_API_VERSION;

    // The path of the directory to store the cache. This must be specified.
    // Please read the documentation on preferred values.
    config.cache_location = selftest ? "" : "tmp";

    // The path of the directory to store the settings. 
    // This must be specified.
    // Please read the documentation on preferred values.
    config.settings_location = selftest ? "" : "tmp";

    // The key of the application. They are generated by Spotify,
    // and are specific to each application using libspotify.
    config.application_key = g_appkey;
    config.application_key_size = g_appkey_size;

    // This identifies the application using some
    // free-text string [1, 255] characters.
    config.user_agent = "spshell";

//ifdef SP_WITH_CURL
#ifdef SP_WITH_CURL
    config.ca_certs_filename = "../cacerts.pem";
#endif
//endif

    // Register the callbacks.
    callbacks.logged_in = logged_in;
    callbacks.logged_out = logged_out;
    callbacks.metadata_updated = metadata_updated;
    callbacks.connection_error = connection_error;
    callbacks.notify_main_thread = notify_main_thread;
#if WITH_TEST_COMMAND
    callbacks.music_delivery = music_delivery;
    callbacks.play_token_lost = play_token_lost;
    callbacks.end_of_track = end_of_track;
#endif
    callbacks.log_message = log_message;
    callbacks.offline_status_updated = offline_status_updated;
    callbacks.credentials_blob_updated = credentials_blob_updated;
//ifdef SP_LIBSPOTIFY_WITH_SCROBBLING
#ifdef SP_LIBSPOTIFY_WITH_SCROBBLING
    callbacks.scrobble_error = scrobble_error;
    callbacks.private_session_mode_changed = private_session_mode_changed;
#endif
//endif
    config.callbacks = &callbacks;

    error = sp_session_create(&config, &session);
    if (SP_ERROR_OK != error) {
        fprintf(stderr, "failed to create session: %s\n",
                        sp_error_message(error));
        return 2;
    }

Parameters:
[in] config The configuration to use for the session
[out] sess If successful, a new session - otherwise NULL
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_BAD_API_VERSION SP_ERROR_BAD_USER_AGENT SP_ERROR_BAD_APPLICATION_KEY SP_ERROR_API_INITIALIZATION_FAILED SP_ERROR_INVALID_DEVICE_ID
Examples:
jukebox.c.
sp_error sp_session_flush_caches ( sp_session session  ) 

Flush the caches

This will make libspotify write all data that is meant to be stored on disk to the disk immediately. libspotify does this periodically by itself and also on logout. So under normal conditions this should never need to be used.

Parameters:
[in] session Your session object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_session_forget_me ( sp_session session  ) 

Remove stored credentials in libspotify. If no credentials are currently stored, nothing will happen.

Parameters:
[in] session Your session object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
bool sp_session_get_volume_normalization ( sp_session session  ) 

Return status of volume normalization

Parameters:
[in] session Session object
Returns:
true iff volume normalization is enabled
sp_playlist* sp_session_inbox_create ( sp_session session  ) 

Returns an inbox playlist for the currently logged in user

Parameters:
[in] session Session object
Returns:
A playlist or NULL if no user is logged in
Note:
You need to release the playlist when you are done with it.
See also:
sp_playlist_release()
bool sp_session_is_private_session ( sp_session session  ) 

Return True if private session is enabled

Parameters:
[in] session Session object
Returns:
True if private session is enabled
sp_error sp_session_is_scrobbling ( sp_session session,
sp_social_provider  provider,
sp_scrobbling_state *  state 
)

Return the scrobbling state. This makes it possible to find out if scrobbling is locally overrided or if the global setting is used.

Parameters:
[in] session Session object
[in] provider The scrobbling provider referred to
[out] state The output variable receiving the sp_scrobbling_state state
Returns:
error code
sp_error sp_session_is_scrobbling_possible ( sp_session session,
sp_social_provider  provider,
bool *  out 
)

Return True if scrobbling settings should be shown to the user. Currently this setting is relevant only to the facebook provider. The returned value may be false if the user is not connected to facebook, or if the user has opted out from facebook social graph.

Parameters:
[in] session Session object
[in] provider The scrobbling provider referred to
[out] out True iff scrobbling is possible
Returns:
error code
sp_error sp_session_login ( sp_session session,
const char *  username,
const char *  password,
bool  remember_me,
const char *  blob 
)

Logs in the specified username/password combo. This initiates the login in the background. A callback is called when login is complete

An application MUST NEVER store the user's password in clear text. If automatic relogin is required, use sp_session_relogin()

Here is a snippet from spshell.c:

        sp_session_login(session, username, password, 1, blob);
    }

Parameters:
[in] session Your session object
[in] username The username to log in
[in] password The password for the specified username
[in] remember_me If set, the username / password will be remembered by libspotify
[in] blob If you have received a blob in the credentials_blob_updated you can pas this here instead of password
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
jukebox.c.
sp_error sp_session_logout ( sp_session session  ) 

Logs out the currently logged in user

Always call this before terminating the application and libspotify is currently logged in. Otherwise, the settings and cache may be lost.

Parameters:
[in] session Your session object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_session_player_load ( sp_session session,
sp_track track 
)

Loads the specified track

After successfully loading the track, you have the option of running sp_session_player_play() directly, or using sp_session_player_seek() first. When this call returns, the track will have been loaded, unless an error occurred.

Parameters:
[in] session Your session object
[in] track The track to be loaded
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_MISSING_CALLBACK SP_ERROR_TRACK_NOT_PLAYABLE
Examples:
jukebox.c.
sp_error sp_session_player_play ( sp_session session,
bool  play 
)

Play or pause the currently loaded track

Parameters:
[in] session Your session object
[in] play If set to true, playback will occur. If set to false, the playback will be paused.
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
jukebox.c.
sp_error sp_session_player_prefetch ( sp_session session,
sp_track track 
)

Prefetch a track

Instruct libspotify to start loading of a track into its cache. This could be done by an application just before the current track ends.

Parameters:
[in] session Your session object
[in] track The track to be prefetched
Returns:
One of the following errors, from sp_error SP_ERROR_NO_CACHE SP_ERROR_OK
Note:
Prefetching is only possible if a cache is configured
sp_error sp_session_player_seek ( sp_session session,
int  offset 
)

Seek to position in the currently loaded track

Parameters:
[in] session Your session object
[in] offset Track position, in milliseconds.
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_session_player_unload ( sp_session session  ) 

Stops the currently playing track

This frees some resources held by libspotify to identify the currently playing track.

Parameters:
[in] session Your session object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
jukebox.c.
sp_playlistcontainer* sp_session_playlistcontainer ( sp_session session  ) 

Returns the playlist container for the currently logged in user.

Parameters:
[in] session Your session object
Returns:
Playlist container object, NULL if not logged in
Examples:
jukebox.c.
sp_error sp_session_preferred_bitrate ( sp_session session,
sp_bitrate  bitrate 
)

Set preferred bitrate for music streaming

Parameters:
[in] session Session object
[in] bitrate Preferred bitrate, see sp_bitrate for possible values
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INVALID_ARGUMENT
sp_error sp_session_preferred_offline_bitrate ( sp_session session,
sp_bitrate  bitrate,
bool  allow_resync 
)

Set preferred bitrate for offline sync

Parameters:
[in] session Session object
[in] bitrate Preferred bitrate, see sp_bitrate for possible values
[in] allow_resync Set to true if libspotify should resynchronize already synchronized tracks. Usually you should set this to false.
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_INVALID_ARGUMENT
sp_error sp_session_process_events ( sp_session session,
int *  next_timeout 
)

Make the specified session process any pending events

Parameters:
[in] session Your session object
[out] next_timeout Stores the time (in milliseconds) until you should call this function again
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
jukebox.c.
sp_playlistcontainer* sp_session_publishedcontainer_for_user_create ( sp_session session,
const char *  canonical_username 
)

Return the published container for a given canonical_username, or the currently logged in user if canonical_username is NULL.

When done with the list you should call sp_playlistconatiner_release() to decrese the reference you own by having created it.

Parameters:
[in] session Your session object.
[in] canonical_username The canonical username, or NULL.
Returns:
Playlist container object, NULL if not logged in.
sp_error sp_session_release ( sp_session sess  ) 

Release the session. This will clean up all data and connections associated with the session

Parameters:
[in] sess Session object returned from sp_session_create()
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_session_relogin ( sp_session session  ) 

Log in the remembered user if last user that logged in logged in with remember_me set. If no credentials are stored, this will return SP_ERROR_NO_CREDENTIALS.

Parameters:
[in] session Your session object
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_NO_CREDENTIALS
int sp_session_remembered_user ( sp_session session,
char *  buffer,
size_t  buffer_size 
)

Get username of the user that will be logged in via sp_session_relogin()

Parameters:
[in] session Your session object
[out] buffer The buffer to hold the username
[in] buffer_size The max size of the buffer that will hold the username. The resulting string is guaranteed to always be null terminated if buffer_size > 0
Returns:
The number of characters in the username. If value is greater or equal than buffer_size, output was truncated. If returned value is -1 no credentials are stored in libspotify.
sp_error sp_session_set_cache_size ( sp_session session,
size_t  size 
)

Set maximum cache size.

Parameters:
[in] session Your session object
[in] size Maximum cache size in megabytes. Setting it to 0 (the default) will let libspotify automatically resize the cache (10% of disk free space)
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_session_set_connection_rules ( sp_session session,
sp_connection_rules  rules 
)

Set rules for how libspotify connects to Spotify servers and synchronizes offline content

Parameters:
[in] session Session object
[in] rules Connection rules
Note:
Used in conjunction with sp_session_set_connection_type() to control how libspotify should behave in respect to network activity and offline synchronization.
See also:
sp_connection_rules
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_session_set_connection_type ( sp_session session,
sp_connection_type  type 
)

Set to true if the connection is currently routed over a roamed connectivity

Parameters:
[in] session Session object
[in] type Connection type
Note:
Used in conjunction with sp_session_set_connection_rules() to control how libspotify should behave in respect to network activity and offline synchronization.
See also:
sp_connection_type
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_session_set_private_session ( sp_session session,
bool  enabled 
)

Set if private session is enabled. This disables sharing what the user is listening to to services such as Spotify Social, Facebook and LastFM. The private session will last for a time, and then libspotify will revert to the normal state. The private session is prolonged by user activity.

Parameters:
[in] session Session object
[in] enabled True iff private session should be enabled
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_session_set_scrobbling ( sp_session session,
sp_social_provider  provider,
sp_scrobbling_state  state 
)

Set if scrobbling is enabled. This api allows setting local overrides of the global scrobbling settings. Changing the global settings are currently not supported.

Parameters:
[in] session Session object
[in] provider The scrobbling provider referred to
[in] state The state to set the provider to
Returns:
error code
See also:
sp_social_provider
sp_scrobbling_state
sp_error sp_session_set_social_credentials ( sp_session session,
sp_social_provider  provider,
const char *  username,
const char *  password 
)

Set the user's credentials with a social provider. Currently this is only relevant for LastFm Call sp_session_set_scrobbling to force an authentication attempt with the LastFm server. If authentication fails a scrobble_error callback will be sent.

Parameters:
[in] session Session object
[in] provider The scrobbling provider referred to
[in] username The user name
[in] password The password
Returns:
error code
See also:
sp_session_set_scrobbling
sp_session_callbacks::scrobble_error
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_error sp_session_set_volume_normalization ( sp_session session,
bool  on 
)

Set volume normalization

Parameters:
[in] session Session object
[in] on True iff volume normalization should be enabled
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_playlist* sp_session_starred_create ( sp_session session  ) 

Returns the starred list for the current user

Parameters:
[in] session Session object
Returns:
A playlist or NULL if no user is logged in
Note:
You need to release the playlist when you are done with it.
See also:
sp_playlist_release()
sp_playlist* sp_session_starred_for_user_create ( sp_session session,
const char *  canonical_username 
)

Returns the starred list for a user

Parameters:
[in] session Session object
[in] canonical_username Canonical username
Returns:
A playlist or NULL if no user is logged in
Note:
You need to release the playlist when you are done with it.
See also:
sp_playlist_release()
sp_user* sp_session_user ( sp_session session  ) 

Fetches the currently logged in user

Parameters:
[in] session Your session object
Returns:
The logged in user (or NULL if not logged in)
int sp_session_user_country ( sp_session session  ) 

Get currently logged in users country updated the offline_status_updated() callback will be invoked.

Parameters:
[in] session Session object
Returns:
Country encoded in an integer 'SE' = 'S' << 8 | 'E'
const char* sp_session_user_name ( sp_session session  ) 

Get a pointer to a string representing the user's login username.

Parameters:
[in] session Your session object
Returns:
A string representing the login username.
void* sp_session_userdata ( sp_session session  ) 

The userdata associated with the session

Parameters:
[in] session Your session object
Returns:
The userdata that was passed in on session creation

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__toplist.html ================================================ libspotify: Toplist handling

Toplist handling

Defines

#define SP_TOPLIST_REGION(a, b)   ((a) << 8 | (b))

Typedefs

typedef void toplistbrowse_complete_cb (sp_toplistbrowse *result, void *userdata)

Enumerations

enum  sp_toplisttype {
  SP_TOPLIST_TYPE_ARTISTS = 0,
  SP_TOPLIST_TYPE_ALBUMS = 1,
  SP_TOPLIST_TYPE_TRACKS = 2
}
enum  sp_toplistregion {
  SP_TOPLIST_REGION_EVERYWHERE = 0,
  SP_TOPLIST_REGION_USER = 1
}

Functions

sp_toplistbrowsesp_toplistbrowse_create (sp_session *session, sp_toplisttype type, sp_toplistregion region, const char *username, toplistbrowse_complete_cb *callback, void *userdata)
bool sp_toplistbrowse_is_loaded (sp_toplistbrowse *tlb)
sp_error sp_toplistbrowse_error (sp_toplistbrowse *tlb)
sp_error sp_toplistbrowse_add_ref (sp_toplistbrowse *tlb)
sp_error sp_toplistbrowse_release (sp_toplistbrowse *tlb)
int sp_toplistbrowse_num_artists (sp_toplistbrowse *tlb)
sp_artistsp_toplistbrowse_artist (sp_toplistbrowse *tlb, int index)
int sp_toplistbrowse_num_albums (sp_toplistbrowse *tlb)
sp_albumsp_toplistbrowse_album (sp_toplistbrowse *tlb, int index)
int sp_toplistbrowse_num_tracks (sp_toplistbrowse *tlb)
sp_tracksp_toplistbrowse_track (sp_toplistbrowse *tlb, int index)
int sp_toplistbrowse_backend_request_duration (sp_toplistbrowse *tlb)

Define Documentation

#define SP_TOPLIST_REGION (   a,
  b 
)    ((a) << 8 | (b))

Convenience macro to create a toplist region. Toplist regions are ISO 3166-1 country codes (in uppercase) encoded in an integer. There are also some reserved codes used to denote non-country regions. See sp_toplistregion

Example: SP_TOPLIST_REGION('S', 'E') for Sweden

Examples:
toplist.c.

Typedef Documentation

typedef void toplistbrowse_complete_cb(sp_toplistbrowse *result, void *userdata)

The type of a callback used in sp_toplistbrowse_create()

When the callback is called, the metadata of all tracks belonging to it will have been loaded, so sp_track_is_loaded() will return non-zero. The same goes for the similar toplist data.

Parameters:
[in] result The same pointer returned by sp_toplistbrowse_create()
[in] userdata The opaque pointer given to sp_toplistbrowse_create()

Enumeration Type Documentation

Special toplist regions

Enumerator:
SP_TOPLIST_REGION_EVERYWHERE 

Global toplist.

SP_TOPLIST_REGION_USER 

Toplist for a given user.

Toplist types

Enumerator:
SP_TOPLIST_TYPE_ARTISTS 

Top artists.

SP_TOPLIST_TYPE_ALBUMS 

Top albums.

SP_TOPLIST_TYPE_TRACKS 

Top tracks.


Function Documentation

sp_error sp_toplistbrowse_add_ref ( sp_toplistbrowse tlb  ) 

Increase the reference count of an toplist browse result

Parameters:
[in] tlb The toplist browse result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
sp_album* sp_toplistbrowse_album ( sp_toplistbrowse tlb,
int  index 
)

Return the album at the given index in the given toplist browse object

Parameters:
[in] tlb Toplist object
[in] index Index of the wanted album. Should be in the interval [0, sp_toplistbrowse_num_albums() - 1]
Returns:
The album at the given index in the given toplist browse object
Examples:
toplist.c.
sp_artist* sp_toplistbrowse_artist ( sp_toplistbrowse tlb,
int  index 
)

Return the artist at the given index in the given toplist browse object

Parameters:
[in] tlb Toplist object
[in] index Index of the wanted artist. Should be in the interval [0, sp_toplistbrowse_num_artists() - 1]
Returns:
The artist at the given index in the given toplist browse object
Examples:
toplist.c.
int sp_toplistbrowse_backend_request_duration ( sp_toplistbrowse tlb  ) 

Return the time (in ms) that was spent waiting for the Spotify backend to serve the request

Parameters:
[in] tlb Toplist object
Returns:
-1 if the request was served from the local cache If the result is not yet loaded the return value is undefined
sp_toplistbrowse* sp_toplistbrowse_create ( sp_session session,
sp_toplisttype  type,
sp_toplistregion  region,
const char *  username,
toplistbrowse_complete_cb callback,
void *  userdata 
)

Initiate a request for browsing an toplist

The user is responsible for freeing the returned toplist browse using sp_toplistbrowse_release(). This can be done in the callback.

Parameters:
[in] session Session object
[in] type Type of toplist to be browsed. see the sp_toplisttype enum for possible values
[in] region Region. see sp_toplistregion enum. Country specific regions are coded as two chars in an integer. Sweden would correspond to 'S' << 8 | 'E'
[in] username If region is SP_TOPLIST_REGION_USER this specifies which user to get toplists for. NULL means the logged in user.
[in] callback Callback to be invoked when browsing has been completed. Pass NULL if you are not interested in this event.
[in] userdata Userdata passed to callback.
Returns:
Toplist browse object
See also:
toplistbrowse_complete_cb
Examples:
toplist.c.
sp_error sp_toplistbrowse_error ( sp_toplistbrowse tlb  ) 

Check if browsing returned an error code.

Parameters:
[in] tlb Toplist browse object
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_IS_LOADING SP_ERROR_OTHER_PERMANENT SP_ERROR_OTHER_TRANSIENT
bool sp_toplistbrowse_is_loaded ( sp_toplistbrowse tlb  ) 

Check if an toplist browse request is completed

Parameters:
[in] tlb Toplist browse object
Returns:
True if browsing is completed, false if not
int sp_toplistbrowse_num_albums ( sp_toplistbrowse tlb  ) 

Given an toplist browse object, return number of albums

Parameters:
[in] tlb Toplist browse object
Returns:
Number of albums on toplist
Examples:
toplist.c.
int sp_toplistbrowse_num_artists ( sp_toplistbrowse tlb  ) 

Given an toplist browse object, return number of artists

Parameters:
[in] tlb Toplist browse object
Returns:
Number of artists on toplist
Examples:
toplist.c.
int sp_toplistbrowse_num_tracks ( sp_toplistbrowse tlb  ) 

Given an toplist browse object, return number of tracks

Parameters:
[in] tlb Toplist browse object
Returns:
Number of tracks on toplist
Examples:
toplist.c.
sp_error sp_toplistbrowse_release ( sp_toplistbrowse tlb  ) 

Decrease the reference count of an toplist browse result

Parameters:
[in] tlb The toplist browse result object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
toplist.c.
sp_track* sp_toplistbrowse_track ( sp_toplistbrowse tlb,
int  index 
)

Return the track at the given index in the given toplist browse object

Parameters:
[in] tlb Toplist object
[in] index Index of the wanted track. Should be in the interval [0, sp_toplistbrowse_num_tracks() - 1]
Returns:
The track at the given index in the given toplist browse object
Examples:
toplist.c.

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__track.html ================================================ libspotify: Track subsystem

Track subsystem

Functions

bool sp_track_is_loaded (sp_track *track)
sp_error sp_track_error (sp_track *track)
sp_track_offline_status sp_track_offline_get_status (sp_track *track)
sp_track_availability sp_track_get_availability (sp_session *session, sp_track *track)
bool sp_track_is_local (sp_session *session, sp_track *track)
bool sp_track_is_autolinked (sp_session *session, sp_track *track)
sp_tracksp_track_get_playable (sp_session *session, sp_track *track)
bool sp_track_is_placeholder (sp_track *track)
bool sp_track_is_starred (sp_session *session, sp_track *track)
sp_error sp_track_set_starred (sp_session *session, sp_track *const *tracks, int num_tracks, bool star)
int sp_track_num_artists (sp_track *track)
sp_artistsp_track_artist (sp_track *track, int index)
sp_albumsp_track_album (sp_track *track)
const char * sp_track_name (sp_track *track)
int sp_track_duration (sp_track *track)
int sp_track_popularity (sp_track *track)
int sp_track_disc (sp_track *track)
int sp_track_index (sp_track *track)
sp_tracksp_localtrack_create (const char *artist, const char *title, const char *album, int length)
sp_error sp_track_add_ref (sp_track *track)
sp_error sp_track_release (sp_track *track)

Function Documentation

sp_track* sp_localtrack_create ( const char *  artist,
const char *  title,
const char *  album,
int  length 
)

Returns the newly created local track

Parameters:
[in] artist Name of the artist
[in] title Song title
[in] album Name of the album, or an empty string if not available
[in] length Length in MS, or -1 if not available.
Returns:
A track.
sp_error sp_track_add_ref ( sp_track track  ) 

Increase the reference count of a track

Parameters:
[in] track The track object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
browse.c.
sp_album* sp_track_album ( sp_track track  ) 

The album of the specified track

Parameters:
[in] track A track object
Returns:
The album of the given track. You need to increase the refcount if you want to keep the pointer around. If no metadata is available for the track yet, this function returns 0.
sp_artist* sp_track_artist ( sp_track track,
int  index 
)

The artist matching the specified index performing on the current track.

Parameters:
[in] track The track whose participating artist you are interested in
[in] index The index for the participating artist. Should be in the interval [0, sp_track_num_artists() - 1]
Returns:
The participating artist, or NULL if invalid index
Examples:
browse.c.
int sp_track_disc ( sp_track track  ) 

Returns the disc number for a track

Parameters:
[in] track A track object
Returns:
Disc index. Possible values are [1, total number of discs on album] This function returns valid data only for tracks appearing in a browse artist or browse album result (otherwise returns 0).
Examples:
browse.c.
int sp_track_duration ( sp_track track  ) 

The duration, in milliseconds, of the specified track

Parameters:
[in] track A track object
Returns:
The duration of the specified track, in milliseconds If no metadata is available for the track yet, this function returns 0.
Examples:
browse.c.
sp_error sp_track_error ( sp_track track  ) 

Return an error code associated with a track. For example if it could not load

Parameters:
[in] track The track
Returns:
One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_IS_LOADING SP_ERROR_OTHER_PERMANENT
Examples:
browse.c, and jukebox.c.
sp_track_availability sp_track_get_availability ( sp_session session,
sp_track track 
)

Return availability for a track

Parameters:
[in] session Session
[in] track The track
Returns:
Availability status, see sp_track_availability
Note:
The track must be loaded or this function will always SP_TRACK_AVAILABILITY_UNAVAILABLE
See also:
sp_track_is_loaded()
sp_track* sp_track_get_playable ( sp_session session,
sp_track track 
)

Return the actual track that will be played if the given track is played

Parameters:
[in] session Session
[in] track The track
Returns:
A track
int sp_track_index ( sp_track track  ) 

Returns the position of a track on its disc

Parameters:
[in] track A track object
Returns:
Track position, starts at 1 (relative the corresponding disc) This function returns valid data only for tracks appearing in a browse artist or browse album result (otherwise returns 0).
Examples:
browse.c.
bool sp_track_is_autolinked ( sp_session session,
sp_track track 
)

Return true if the track is autolinked to another track.

Parameters:
[in] session Session
[in] track The track
Returns:
True if track is autolinked.
Note:
The track must be loaded or this function will always return false.
See also:
sp_track_is_loaded()
bool sp_track_is_loaded ( sp_track track  ) 

Return whether or not the track metadata is loaded.

Parameters:
[in] track The track
Returns:
True if track is loaded
Note:
This is equivalent to checking if sp_track_error() not returns SP_ERROR_IS_LOADING.
Examples:
browse.c.
bool sp_track_is_local ( sp_session session,
sp_track track 
)

Return true if the track is a local file.

Parameters:
[in] session Session
[in] track The track
Returns:
True if track is a local file.
Note:
The track must be loaded or this function will always return false.
See also:
sp_track_is_loaded()
bool sp_track_is_placeholder ( sp_track track  ) 

Return true if the track is a placeholder. Placeholder tracks are used to store other objects than tracks in the playlist. Currently this is used in the inbox to store artists, albums and playlists.

Use sp_link_create_from_track() to get a link object that points to the real object this "track" points to.

Parameters:
[in] track The track
Returns:
True if track is a placeholder
Note:
Contrary to most functions the track does not have to be loaded for this function to return correct value
bool sp_track_is_starred ( sp_session session,
sp_track track 
)

Return true if the track is starred by the currently logged in user.

Parameters:
[in] session Session
[in] track The track
Returns:
True if track is starred.
Note:
The track must be loaded or this function will always return false.
See also:
sp_track_is_loaded()
Examples:
browse.c.
const char* sp_track_name ( sp_track track  ) 

The string representation of the specified track's name

Parameters:
[in] track A track object
Returns:
The string representation of the specified track's name. Returned string is valid as long as the album object stays allocated and no longer than the next call to sp_session_process_events() If no metadata is available for the track yet, this function returns empty string.
Examples:
browse.c, and jukebox.c.
int sp_track_num_artists ( sp_track track  ) 

The number of artists performing on the specified track

Parameters:
[in] track The track whose number of participating artists you are interested in
Returns:
The number of artists performing on the specified track. If no metadata is available for the track yet, this function returns 0.
Examples:
browse.c.
sp_track_offline_status sp_track_offline_get_status ( sp_track track  ) 

Return offline status for a track. sp_session_callbacks::metadata_updated() will be invoked when offline status of a track changes

Parameters:
[in] track The track
Returns:
Stats as described by sp_track_offline_status
int sp_track_popularity ( sp_track track  ) 

Returns popularity for track

Parameters:
[in] track A track object
Returns:
Popularity in range 0 to 100, 0 if undefined. If no metadata is available for the track yet, this function returns 0.
Examples:
browse.c.
sp_error sp_track_release ( sp_track track  ) 

Decrease the reference count of a track

Parameters:
[in] track The track object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
Examples:
browse.c.
sp_error sp_track_set_starred ( sp_session session,
sp_track *const *  tracks,
int  num_tracks,
bool  star 
)

Star/Unstar the specified track

Parameters:
[in] session Session
[in] tracks Array of pointer to tracks.
[in] num_tracks Length of tracks array
[in] star Starred status of the track
Note:
This will fail silently if playlists are disabled.
See also:
sp_set_playlists_enabled()

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__types.html ================================================ libspotify: Spotify types & structs

Spotify types & structs

Typedefs

typedef struct sp_session sp_session
 Representation of a session.
typedef struct sp_track sp_track
 A track handle.
typedef struct sp_album sp_album
 An album handle.
typedef struct sp_artist sp_artist
 An artist handle.
typedef struct sp_artistbrowse sp_artistbrowse
 A handle to an artist browse result.
typedef struct sp_albumbrowse sp_albumbrowse
 A handle to an album browse result.
typedef struct sp_toplistbrowse sp_toplistbrowse
 A handle to a toplist browse result.
typedef struct sp_search sp_search
 A handle to a search result.
typedef struct sp_link sp_link
 A handle to the libspotify internal representation of a URI.
typedef struct sp_image sp_image
 A handle to an image.
typedef struct sp_user sp_user
 A handle to a user.
typedef struct sp_playlist sp_playlist
 A playlist handle.
typedef struct sp_playlistcontainer sp_playlistcontainer
 A playlist container (playlist containing other playlists) handle.
typedef struct sp_inbox sp_inbox
 Add to inbox request handle.

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/group__user.html ================================================ libspotify: User handling

User handling

Typedefs

typedef enum sp_relation_type sp_relation_type

Enumerations

enum  sp_relation_type {
  SP_RELATION_TYPE_UNKNOWN = 0,
  SP_RELATION_TYPE_NONE = 1,
  SP_RELATION_TYPE_UNIDIRECTIONAL = 2,
  SP_RELATION_TYPE_BIDIRECTIONAL = 3
}

Functions

const char * sp_user_canonical_name (sp_user *user)
const char * sp_user_display_name (sp_user *user)
bool sp_user_is_loaded (sp_user *user)
sp_error sp_user_add_ref (sp_user *user)
sp_error sp_user_release (sp_user *user)

Typedef Documentation

User relation type


Enumeration Type Documentation

User relation type

Enumerator:
SP_RELATION_TYPE_UNKNOWN 

Not yet known.

SP_RELATION_TYPE_NONE 

No relation.

SP_RELATION_TYPE_UNIDIRECTIONAL 

The currently logged in user is following this uer.

SP_RELATION_TYPE_BIDIRECTIONAL 

Bidirectional friendship established.


Function Documentation

sp_error sp_user_add_ref ( sp_user user  ) 

Increase the reference count of an user

Parameters:
[in] user The user object
Returns:
One of the following errors, from sp_error SP_ERROR_OK
const char* sp_user_canonical_name ( sp_user user  ) 

Get a pointer to a string representing the user's canonical username.

Parameters:
[in] user The Spotify user whose canonical username you would like a string representation of
Returns:
A string representing the canonical username.
const char* sp_user_display_name ( sp_user user  ) 

Get a pointer to a string representing the user's displayable username. If there is no difference between the canonical username and the display name, or if the library does not know about the display name yet, the canonical username will be returned.

Parameters:
[in] user The Spotify user whose displayable username you would like a string representation of
Returns:
A string
bool sp_user_is_loaded ( sp_user user  ) 

Get load status for a user object. Before it is loaded, only the user's canonical username is known.

Parameters:
[in] user Spotify user object
Returns:
True if user object is loaded, otherwise false
sp_error sp_user_release ( sp_user user  ) 

Decrease the reference count of an user

Parameters:
[in] user The user object
Returns:
One of the following errors, from sp_error SP_ERROR_OK

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/index.html ================================================ libspotify: libspotify 12.1.51 API Documentation

libspotify 12.1.51 API Documentation

This documentation explains how you can make use of the libspotify C API within an application of your own.

The documentation was generated with Doxygen. You will find a list of submodules in the Modules section. The list of modules are ordered in a reasonable reading order. It begins with some simple types used throughout the rest of the modules, continues with basic error handling and the functions required to manage sessions.

The rest of the modules are specfic parts of libspotify for accessing information about artists, albums, tracks, and playlists. Separate modules are available to handle searches and images.

This initial chapter of libspotify will focus on the general design of the library, and things to take into consideration once you start working with it.

For most of the functionality, there are examples available in .

Issues and Restrictions

A few restrictions apply to the libspotify library. These may be changed (or fixed) in future versions of the library.

  • Only one process can access the cache and settings directory. It is your responsibility to make sure instances of your application, or any other application and your application does not attempt to use the same cache. You can do this using a clever naming schema, or using a lock-file.
  • Even though sp_session_init() creates a new object, there are still some global variables behind the scenes that will stop you from creating multiple sessions within a single process.

General Design

In this section, you will find the overall philosophy of the library with regard to memory management and error handling.

Error Handling

All functions that have some form of useful error state returns an sp_error. The actual result value is returned in an out pointer in these cases. Some functions return pointers where you must check for NULL before using the returned value. Those places should be documented next to each function.

In addition to functions returning an sp_error, some request objects (browse and search objects) have an error accessor. When the object has been loaded, the error code will be set to reflect the success or failure of the request.

A trivial error code to string mapper function exists that works just like strerror(3).

Reference Counting

Reference counting is used for all domain objects in libspotify. Functions including the string create will return an object with a pre-incremented reference count. Thus, each create must have a corresponding release when the value is no longer needed.

Other accessor functions (including sp_link_as_artist et al.), on the other hand, returns a reference borrowed from the object it was retrieved from. Retrieving an sp_album from an sp_link would make the album object survive until the link object is freed, unless its reference count is explicitly incremented.

Threads

The library itself uses multiple threads internally. To allow for synchronization between these threads, you must implement the sp_session_callbacks::notify_main_thread callback. Whenever called (from some internal thread), the application must wake up the main loop so the sp_session_process_events() function can be run.

The API itself is not thread-safe. Thus, you must take care not to call the API functions from more than one of your own threads.

Objects

All objects (tracks, albums, artists, etc) are loaded asynchronously. Therefore the API user must query the object via the _is_loaded() functions to check that the object data has been populated. There is currently no way of finding out when data has been updated for a specific object. Rather, the user need to iterate all objects of interest upon invokation of the metadata_updated() callback.

Also, objects are not populated when created as a result of a sp_link_as_...() method.

Disk Cache Management

Currently the disk cache can only be opened by one process at a time. It is preferrable not to put the cache in a network file system. To avoid clashes, we recommend you to put set the cache location to /var/tmp/username/appname/ and to add some kind of lock. The appname would be a mangled user-agent string.

While you could simply remove the cache when the application exits to avoid the locking issue, your application will be slower as music, playlists and other metadata will have to be loaded from the server on each login. You are strongly encouraged to use a persistent cache.

Settings should be stored in the users home directory, but they should also be kept separate per application. We would recommend ~/.config/appname/libspotify/ for these files on UNIX-like systems or %appdata%\appname\libspotify\ on Windows.

Session Management

Before running any application you will need an application key. An application key allows Spotify to identify your application and should be unique per application. You will find more information about the application keys on our developer website. We have choosen not to distribute the example programs with an application key, as that might cause the key to be misused. We encourage you to get a valid application key, and put it in a file called appkey.c and compile the examples using the included makefile.

In order to ensure all data is correctly synced to disk, we encourage you to actually use sp_session_logout() before terminating your application. Efter logout, you will receive a callback call in which you could display a login box, or terminate.

Images

Images are identified with a const byte* value, returned by various functions in the API (such as sp_album_cover()). The pointers are valid until the object is freed, thus you should keep a reference to the objects until you are no longer using the image ID.

It is also possible to get references to images as URIs (see the sp_link type). This might be favourable if you need to store a reference to an image for later use but want to release the originating object.

Images will always be given to the application compressed using a image compression format. Currently only JPEG encoded images are delivered. See sp_image_format(). The API user should use sp_image_data() to get the encoded data and the application need to do the decoding of the image by itself.

Audio

The audio is delivered through a push-callback called by libspotify when data is available. Your callback may eat all data, or just enough to fill some constant-sized buffer. The callback will be called from a libspotify internal thread, so if you share state between the audio callback and the main thread, be sure to add adequate thread synchronization.

Samples are delivered as integers, see sp_audioformat. One frame consists of the same number of samples as there are channels. I.e. interleaving is on the sample level.

Examples

Included in the distribution are a couple of example files designed to get you started easily.

Licenses

The example code distributed with libspotify uses the MIT license. All documentation, libspotify itself, and the associated C header file are distributed under the libspotify Terms of Use.


Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/jukebox_8c-example.html ================================================ libspotify: jukebox.c

jukebox.c

The jukebox.c example shows how you can use playback and playlist functions.

#include <errno.h>
#include <libgen.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>

#include <libspotify/api.h>

#include "audio.h"


/* --- Data --- */
extern const uint8_t g_appkey[];
extern const size_t g_appkey_size;

static audio_fifo_t g_audiofifo;
static pthread_mutex_t g_notify_mutex;
static pthread_cond_t g_notify_cond;
static int g_notify_do;
static int g_playback_done;
static sp_session *g_sess;
static sp_playlist *g_jukeboxlist;
const char *g_listname;
static int g_remove_tracks = 0;
static sp_track *g_currenttrack;
static int g_track_index;


static void try_jukebox_start(void)
{
    sp_track *t;

    if (!g_jukeboxlist)
        return;

    if (!sp_playlist_num_tracks(g_jukeboxlist)) {
        fprintf(stderr, "jukebox: No tracks in playlist. Waiting\n");
        return;
    }

    if (sp_playlist_num_tracks(g_jukeboxlist) < g_track_index) {
        fprintf(stderr, "jukebox: No more tracks in playlist. Waiting\n");
        return;
    }

    t = sp_playlist_track(g_jukeboxlist, g_track_index);

    if (g_currenttrack && t != g_currenttrack) {
        /* Someone changed the current track */
        audio_fifo_flush(&g_audiofifo);
        sp_session_player_unload(g_sess);
        g_currenttrack = NULL;
    }

    if (!t)
        return;

    if (sp_track_error(t) != SP_ERROR_OK)
        return;

    if (g_currenttrack == t)
        return;

    g_currenttrack = t;

    printf("jukebox: Now playing \"%s\"...\n", sp_track_name(t));
    fflush(stdout);

    sp_session_player_load(g_sess, t);
    sp_session_player_play(g_sess, 1);
}

/* --------------------------  PLAYLIST CALLBACKS  ------------------------- */
static void tracks_added(sp_playlist *pl, sp_track * const *tracks,
                         int num_tracks, int position, void *userdata)
{
    if (pl != g_jukeboxlist)
        return;

    printf("jukebox: %d tracks were added\n", num_tracks);
    fflush(stdout);
    try_jukebox_start();
}

static void tracks_removed(sp_playlist *pl, const int *tracks,
                           int num_tracks, void *userdata)
{
    int i, k = 0;

    if (pl != g_jukeboxlist)
        return;

    for (i = 0; i < num_tracks; ++i)
        if (tracks[i] < g_track_index)
            ++k;

    g_track_index -= k;

    printf("jukebox: %d tracks were removed\n", num_tracks);
    fflush(stdout);
    try_jukebox_start();
}

static void tracks_moved(sp_playlist *pl, const int *tracks,
                         int num_tracks, int new_position, void *userdata)
{
    if (pl != g_jukeboxlist)
        return;

    printf("jukebox: %d tracks were moved around\n", num_tracks);
    fflush(stdout);
    try_jukebox_start();
}

static void playlist_renamed(sp_playlist *pl, void *userdata)
{
    const char *name = sp_playlist_name(pl);

    if (!strcasecmp(name, g_listname)) {
        g_jukeboxlist = pl;
        g_track_index = 0;
        try_jukebox_start();
    } else if (g_jukeboxlist == pl) {
        printf("jukebox: current playlist renamed to \"%s\".\n", name);
        g_jukeboxlist = NULL;
        g_currenttrack = NULL;
        sp_session_player_unload(g_sess);
    }
}

static sp_playlist_callbacks pl_callbacks = {
    .tracks_added = &tracks_added,
    .tracks_removed = &tracks_removed,
    .tracks_moved = &tracks_moved,
    .playlist_renamed = &playlist_renamed,
};


/* --------------------  PLAYLIST CONTAINER CALLBACKS  --------------------- */
static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl,
                           int position, void *userdata)
{
    sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);

    if (!strcasecmp(sp_playlist_name(pl), g_listname)) {
        g_jukeboxlist = pl;
        try_jukebox_start();
    }
}

static void playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl,
                             int position, void *userdata)
{
    sp_playlist_remove_callbacks(pl, &pl_callbacks, NULL);
}


static void container_loaded(sp_playlistcontainer *pc, void *userdata)
{
    fprintf(stderr, "jukebox: Rootlist synchronized (%d playlists)\n",
        sp_playlistcontainer_num_playlists(pc));
}


static sp_playlistcontainer_callbacks pc_callbacks = {
    .playlist_added = &playlist_added,
    .playlist_removed = &playlist_removed,
    .container_loaded = &container_loaded,
};


/* ---------------------------  SESSION CALLBACKS  ------------------------- */
static void logged_in(sp_session *sess, sp_error error)
{
    sp_playlistcontainer *pc = sp_session_playlistcontainer(sess);
    int i;

    if (SP_ERROR_OK != error) {
        fprintf(stderr, "jukebox: Login failed: %s\n",
            sp_error_message(error));
        exit(2);
    }

    sp_playlistcontainer_add_callbacks(
        pc,
        &pc_callbacks,
        NULL);

    printf("jukebox: Looking at %d playlists\n", sp_playlistcontainer_num_playlists(pc));

    for (i = 0; i < sp_playlistcontainer_num_playlists(pc); ++i) {
        sp_playlist *pl = sp_playlistcontainer_playlist(pc, i);

        sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);

        if (!strcasecmp(sp_playlist_name(pl), g_listname)) {
            g_jukeboxlist = pl;
            try_jukebox_start();
        }
    }

    if (!g_jukeboxlist) {
        printf("jukebox: No such playlist. Waiting for one to pop up...\n");
        fflush(stdout);
    }
}

static void notify_main_thread(sp_session *sess)
{
    pthread_mutex_lock(&g_notify_mutex);
    g_notify_do = 1;
    pthread_cond_signal(&g_notify_cond);
    pthread_mutex_unlock(&g_notify_mutex);
}

static int music_delivery(sp_session *sess, const sp_audioformat *format,
                          const void *frames, int num_frames)
{
    audio_fifo_t *af = &g_audiofifo;
    audio_fifo_data_t *afd;
    size_t s;

    if (num_frames == 0)
        return 0; // Audio discontinuity, do nothing

    pthread_mutex_lock(&af->mutex);

    /* Buffer one second of audio */
    if (af->qlen > format->sample_rate) {
        pthread_mutex_unlock(&af->mutex);

        return 0;
    }

    s = num_frames * sizeof(int16_t) * format->channels;

    afd = malloc(sizeof(*afd) + s);
    memcpy(afd->samples, frames, s);

    afd->nsamples = num_frames;

    afd->rate = format->sample_rate;
    afd->channels = format->channels;

    TAILQ_INSERT_TAIL(&af->q, afd, link);
    af->qlen += num_frames;

    pthread_cond_signal(&af->cond);
    pthread_mutex_unlock(&af->mutex);

    return num_frames;
}


static void end_of_track(sp_session *sess)
{
    pthread_mutex_lock(&g_notify_mutex);
    g_playback_done = 1;
    g_notify_do = 1;
    pthread_cond_signal(&g_notify_cond);
    pthread_mutex_unlock(&g_notify_mutex);
}


static void metadata_updated(sp_session *sess)
{
    try_jukebox_start();
}

static void play_token_lost(sp_session *sess)
{
    audio_fifo_flush(&g_audiofifo);

    if (g_currenttrack != NULL) {
        sp_session_player_unload(g_sess);
        g_currenttrack = NULL;
    }
}

static sp_session_callbacks session_callbacks = {
    .logged_in = &logged_in,
    .notify_main_thread = &notify_main_thread,
    .music_delivery = &music_delivery,
    .metadata_updated = &metadata_updated,
    .play_token_lost = &play_token_lost,
    .log_message = NULL,
    .end_of_track = &end_of_track,
};

static sp_session_config spconfig = {
    .api_version = SPOTIFY_API_VERSION,
    .cache_location = "tmp",
    .settings_location = "tmp",
    .application_key = g_appkey,
    .application_key_size = 0, // Set in main()
    .user_agent = "spotify-jukebox-example",
    .callbacks = &session_callbacks,
    NULL,
};
/* -------------------------  END SESSION CALLBACKS  ----------------------- */


static void track_ended(void)
{
    int tracks = 0;

    if (g_currenttrack) {
        g_currenttrack = NULL;
        sp_session_player_unload(g_sess);
        if (g_remove_tracks) {
            sp_playlist_remove_tracks(g_jukeboxlist, &tracks, 1);
        } else {
            ++g_track_index;
            try_jukebox_start();
        }
    }
}

static void usage(const char *progname)
{
    fprintf(stderr, "usage: %s -u <username> -p <password> -l <listname> [-d]\n", progname);
    fprintf(stderr, "warning: -d will delete the tracks played from the list!\n");
}

int main(int argc, char **argv)
{
    sp_session *sp;
    sp_error err;
    int next_timeout = 0;
    const char *username = NULL;
    const char *password = NULL;
    int opt;

    while ((opt = getopt(argc, argv, "u:p:l:d")) != EOF) {
        switch (opt) {
        case 'u':
            username = optarg;
            break;

        case 'p':
            password = optarg;
            break;

        case 'l':
            g_listname = optarg;
            break;

        case 'd':
            g_remove_tracks = 1;
            break;

        default:
            exit(1);
        }
    }

    if (!username || !password || !g_listname) {
        usage(basename(argv[0]));
        exit(1);
    }

    audio_init(&g_audiofifo);

    /* Create session */
    spconfig.application_key_size = g_appkey_size;

    err = sp_session_create(&spconfig, &sp);

    if (SP_ERROR_OK != err) {
        fprintf(stderr, "Unable to create session: %s\n",
            sp_error_message(err));
        exit(1);
    }

    g_sess = sp;

    pthread_mutex_init(&g_notify_mutex, NULL);
    pthread_cond_init(&g_notify_cond, NULL);

    sp_session_login(sp, username, password, 0, NULL);
    pthread_mutex_lock(&g_notify_mutex);

    for (;;) {
        if (next_timeout == 0) {
            while(!g_notify_do)
                pthread_cond_wait(&g_notify_cond, &g_notify_mutex);
        } else {
            struct timespec ts;

#if _POSIX_TIMERS > 0
            clock_gettime(CLOCK_REALTIME, &ts);
#else
            struct timeval tv;
            gettimeofday(&tv, NULL);
            TIMEVAL_TO_TIMESPEC(&tv, &ts);
#endif
            ts.tv_sec += next_timeout / 1000;
            ts.tv_nsec += (next_timeout % 1000) * 1000000;

            pthread_cond_timedwait(&g_notify_cond, &g_notify_mutex, &ts);
        }

        g_notify_do = 0;
        pthread_mutex_unlock(&g_notify_mutex);

        if (g_playback_done) {
            track_ended();
            g_playback_done = 0;
        }

        do {
            sp_session_process_events(sp, &next_timeout);
        } while (next_timeout == 0);

        pthread_mutex_lock(&g_notify_mutex);
    }

    return 0;
}

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/modules.html ================================================ libspotify: Module Index

Modules


Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/search_8c-example.html ================================================ libspotify: search.c

search.c

The search.c example shows how you can use search functions. It is part of the spshell program

#include <string.h>

#include "spshell.h"
#include "cmd.h"


static void print_album(sp_album *album)
{
    printf("  Album \"%s\" (%d)\n",
           sp_album_name(album),
           sp_album_year(album));
}

static void print_artist(sp_artist *artist)
{
    printf("  Artist \"%s\"\n", sp_artist_name(artist));
}

static void print_search(sp_search *search)
{
    int i;

    printf("Query          : %s\n", sp_search_query(search));
    printf("Did you mean   : %s\n", sp_search_did_you_mean(search));
    printf("Tracks in total: %d\n", sp_search_total_tracks(search));
    puts("");

    for (i = 0; i < sp_search_num_tracks(search); ++i)
        print_track(sp_search_track(search, i));

    puts("");

    for (i = 0; i < sp_search_num_albums(search); ++i)
        print_album(sp_search_album(search, i));

    puts("");

    for (i = 0; i < sp_search_num_artists(search); ++i)
        print_artist(sp_search_artist(search, i));

    puts("");

    for (i = 0; i < sp_search_num_playlists(search); ++i) {
        // print some readily available metadata, the rest will
        // be available from the sp_playlist object loaded through
        // sp_search_playlist().
        printf("  Playlist \"%s\"\n", sp_search_playlist_name(search, i));
    }
}

static void SP_CALLCONV search_complete(sp_search *search, void *userdata)
{
    if (sp_search_error(search) == SP_ERROR_OK)
        print_search(search);
    else
        fprintf(stderr, "Failed to search: %s\n",
                sp_error_message(sp_search_error(search)));

    sp_search_release(search);
    cmd_done();
}



static void search_usage(void)
{
    fprintf(stderr, "Usage: search <query>\n");
}


int cmd_search(int argc, char **argv)
{
    char query[1024];
    int i;

    if (argc < 2) {
        search_usage();
        return -1;
    }

    query[0] = 0;
    for(i = 1; i < argc; i++)
        snprintf(query + strlen(query), sizeof(query) - strlen(query), "%s%s",
             i == 1 ? "" : " ", argv[i]);

    sp_search_create(g_session, query, 0, 100, 0, 100, 0, 100, 0, 100, SP_SEARCH_STANDARD, &search_complete, NULL);
    return 0;
}


int cmd_whatsnew(int argc, char **argv)
{
    sp_search_create(g_session, "tag:new", 0, 0, 0, 250, 0, 0, 0, 0, SP_SEARCH_STANDARD, &search_complete, NULL);
    return 0;
}

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/structsp__audio__buffer__stats.html ================================================ libspotify: sp_audio_buffer_stats Struct Reference

sp_audio_buffer_stats Struct Reference
[Session handling]

#include <api.h>

Data Fields

int samples
 Samples in buffer.
int stutter
 Number of stutters (audio dropouts) since last query.

Detailed Description

Buffer stats used by get_audio_buffer_stats callback


The documentation for this struct was generated from the following file:

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/structsp__audioformat.html ================================================ libspotify: sp_audioformat Struct Reference

sp_audioformat Struct Reference
[Session handling]

#include <api.h>

Data Fields

sp_sampletype sample_type
 Sample type enum,.
int sample_rate
 Audio sample rate, in samples per second.
int channels
 Number of channels. Currently 1 or 2.

Detailed Description

Audio format descriptor

Examples:

jukebox.c.


The documentation for this struct was generated from the following file:

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/structsp__offline__sync__status.html ================================================ libspotify: sp_offline_sync_status Struct Reference

sp_offline_sync_status Struct Reference
[Session handling]

#include <api.h>

Data Fields

int queued_tracks
int done_tracks
int copied_tracks
int willnotcopy_tracks
int error_tracks
bool syncing

Detailed Description

Offline sync status


Field Documentation

Copied tracks/bytes is things that has been copied in current sync operation

Done tracks/bytes is things marked for sync that existed on device before current sync operation

A track is counted as error when something goes wrong while syncing the track

Queued tracks/bytes is things left to sync in current sync operation

Set if sync operation is in progress

Tracks that are marked as synced but will not be copied (for various reasons)


The documentation for this struct was generated from the following file:

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/structsp__playlist__callbacks.html ================================================ libspotify: sp_playlist_callbacks Struct Reference

sp_playlist_callbacks Struct Reference
[Playlist subsystem]

#include <api.h>

Data Fields

void(* tracks_added )(sp_playlist *pl, sp_track *const *tracks, int num_tracks, int position, void *userdata)
void(* tracks_removed )(sp_playlist *pl, const int *tracks, int num_tracks, void *userdata)
void(* tracks_moved )(sp_playlist *pl, const int *tracks, int num_tracks, int new_position, void *userdata)
void(* playlist_renamed )(sp_playlist *pl, void *userdata)
void(* playlist_state_changed )(sp_playlist *pl, void *userdata)
void(* playlist_update_in_progress )(sp_playlist *pl, bool done, void *userdata)
void(* playlist_metadata_updated )(sp_playlist *pl, void *userdata)
void(* track_created_changed )(sp_playlist *pl, int position, sp_user *user, int when, void *userdata)
void(* track_seen_changed )(sp_playlist *pl, int position, bool seen, void *userdata)
void(* description_changed )(sp_playlist *pl, const char *desc, void *userdata)
void(* image_changed )(sp_playlist *pl, const byte *image, void *userdata)
void(* track_message_changed )(sp_playlist *pl, int position, const char *message, void *userdata)
void(* subscribers_changed )(sp_playlist *pl, void *userdata)

Detailed Description

Playlist callbacks

Used to get notifications when playlists are updated. If some callbacks should not be of interest, set them to NULL.

Examples:

browse.c, and jukebox.c.


Field Documentation

void( * sp_playlist_callbacks::description_changed)(sp_playlist *pl, const char *desc, void *userdata)

Called when playlist description has changed

Parameters:
[in] pl Playlist object
[in] desc New description
[in] userdata Userdata passed to sp_playlist_add_callbacks()
void( * sp_playlist_callbacks::image_changed)(sp_playlist *pl, const byte *image, void *userdata)

Called when playlist image has changed

Parameters:
[in] pl Playlist object
[in] image New image
[in] userdata Userdata passed to sp_playlist_add_callbacks()

Called when metadata for one or more tracks in a playlist has been updated.

Parameters:
[in] pl Playlist object
[in] userdata Userdata passed to sp_playlist_add_callbacks()

Called when a playlist has been renamed. sp_playlist_name() can be used to find out the new name

Parameters:
[in] pl Playlist object
[in] userdata Userdata passed to sp_playlist_add_callbacks()

Called when state changed for a playlist.

There are three states that trigger this callback:

  • Collaboration for this playlist has been turned on or off
  • The playlist started having pending changes, or all pending changes have now been committed
  • The playlist started loading, or finished loading
Parameters:
[in] pl Playlist object
[in] userdata Userdata passed to sp_playlist_add_callbacks()
See also:
sp_playlist_is_collaborative
sp_playlist_has_pending_changes
sp_playlist_is_loaded
void( * sp_playlist_callbacks::playlist_update_in_progress)(sp_playlist *pl, bool done, void *userdata)

Called when a playlist is updating or is done updating

This is called before and after a series of changes are applied to the playlist. It allows e.g. the user interface to defer updating until the entire operation is complete.

Parameters:
[in] pl Playlist object
[in] done True iff the update is completed
[in] userdata Userdata passed to sp_playlist_add_callbacks()

Called when playlist subscribers changes (count or list of names)

Parameters:
[in] pl Playlist object
[in] userdata Userdata passed to sp_playlist_add_callbacks()
void( * sp_playlist_callbacks::track_created_changed)(sp_playlist *pl, int position, sp_user *user, int when, void *userdata)

Called when create time and/or creator for a playlist entry changes

Parameters:
[in] pl Playlist object
[in] position Position in playlist
[in] user User object
[in] time When entry was created, seconds since the unix epoch.
[in] userdata Userdata passed to sp_playlist_add_callbacks()
void( * sp_playlist_callbacks::track_message_changed)(sp_playlist *pl, int position, const char *message, void *userdata)

Called when message attribute for a playlist entry changes.

Parameters:
[in] pl Playlist object
[in] position Position in playlist
[in] message UTF-8 encoded message
[in] userdata Userdata passed to sp_playlist_add_callbacks()
void( * sp_playlist_callbacks::track_seen_changed)(sp_playlist *pl, int position, bool seen, void *userdata)

Called when seen attribute for a playlist entry changes.

Parameters:
[in] pl Playlist object
[in] position Position in playlist
[in] seen Set if entry it marked as seen
[in] userdata Userdata passed to sp_playlist_add_callbacks()
void( * sp_playlist_callbacks::tracks_added)(sp_playlist *pl, sp_track *const *tracks, int num_tracks, int position, void *userdata)

Called when one or more tracks have been added to a playlist

Parameters:
[in] pl Playlist object
[in] tracks Array of pointers to track objects
[in] num_tracks Number of entries in tracks
[in] position Position in the playlist for the first track.
[in] userdata Userdata passed to sp_playlist_add_callbacks()
void( * sp_playlist_callbacks::tracks_moved)(sp_playlist *pl, const int *tracks, int num_tracks, int new_position, void *userdata)

Called when one or more tracks have been moved within a playlist

Parameters:
[in] pl Playlist object
[in] tracks Array of positions representing the tracks that were moved
[in] num_tracks Number of entries in tracks
[in] position New position in the playlist for the first track.
[in] userdata Userdata passed to sp_playlist_add_callbacks()
void( * sp_playlist_callbacks::tracks_removed)(sp_playlist *pl, const int *tracks, int num_tracks, void *userdata)

Called when one or more tracks have been removed from a playlist

Parameters:
[in] pl Playlist object
[in] tracks Array of positions representing the tracks that were removed
[in] num_tracks Number of entries in tracks
[in] userdata Userdata passed to sp_playlist_add_callbacks()

The documentation for this struct was generated from the following file:

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/structsp__playlistcontainer__callbacks.html ================================================ libspotify: sp_playlistcontainer_callbacks Struct Reference

sp_playlistcontainer_callbacks Struct Reference
[Playlist subsystem]

#include <api.h>

Data Fields

void(* playlist_added )(sp_playlistcontainer *pc, sp_playlist *playlist, int position, void *userdata)
void(* playlist_removed )(sp_playlistcontainer *pc, sp_playlist *playlist, int position, void *userdata)
void(* playlist_moved )(sp_playlistcontainer *pc, sp_playlist *playlist, int position, int new_position, void *userdata)
void(* container_loaded )(sp_playlistcontainer *pc, void *userdata)

Detailed Description

Playlist container callbacks. If some callbacks should not be of interest, set them to NULL.

See also:
sp_playlistcontainer_add_callbacks
sp_playlistcontainer_remove_callbacks
Examples:

jukebox.c.


Field Documentation

Called when the playlist container is loaded

Parameters:
[in] pc Playlist container
[in] userdata Userdata as set in sp_playlistcontainer_add_callbacks()
void( * sp_playlistcontainer_callbacks::playlist_added)(sp_playlistcontainer *pc, sp_playlist *playlist, int position, void *userdata)

Called when a new playlist has been added to the playlist container.

Parameters:
[in] pc Playlist container
[in] playlist Playlist object.
[in] position Position in list
[in] userdata Userdata as set in sp_playlistcontainer_add_callbacks()
void( * sp_playlistcontainer_callbacks::playlist_moved)(sp_playlistcontainer *pc, sp_playlist *playlist, int position, int new_position, void *userdata)

Called when a playlist has been moved in the playlist container

Parameters:
[in] pc Playlist container
[in] playlist Playlist object.
[in] position Previous position in playlist container list
[in] new_position New position in playlist container list
[in] userdata Userdata as set in sp_playlistcontainer_add_callbacks()
void( * sp_playlistcontainer_callbacks::playlist_removed)(sp_playlistcontainer *pc, sp_playlist *playlist, int position, void *userdata)

Called when a new playlist has been removed from playlist container

Parameters:
[in] pc Playlist container
[in] playlist Playlist object.
[in] position Position in list
[in] userdata Userdata as set in sp_playlistcontainer_add_callbacks()

The documentation for this struct was generated from the following file:

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/structsp__session__callbacks.html ================================================ libspotify: sp_session_callbacks Struct Reference

sp_session_callbacks Struct Reference
[Session handling]

#include <api.h>

Data Fields

void(* logged_in )(sp_session *session, sp_error error)
void(* logged_out )(sp_session *session)
void(* metadata_updated )(sp_session *session)
void(* connection_error )(sp_session *session, sp_error error)
void(* message_to_user )(sp_session *session, const char *message)
void(* notify_main_thread )(sp_session *session)
int(* music_delivery )(sp_session *session, const sp_audioformat *format, const void *frames, int num_frames)
void(* play_token_lost )(sp_session *session)
void(* log_message )(sp_session *session, const char *data)
void(* end_of_track )(sp_session *session)
void(* streaming_error )(sp_session *session, sp_error error)
void(* userinfo_updated )(sp_session *session)
void(* start_playback )(sp_session *session)
void(* stop_playback )(sp_session *session)
void(* get_audio_buffer_stats )(sp_session *session, sp_audio_buffer_stats *stats)
void(* offline_status_updated )(sp_session *session)
void(* offline_error )(sp_session *session, sp_error error)
void(* credentials_blob_updated )(sp_session *session, const char *blob)
void(* connectionstate_updated )(sp_session *session)
void(* scrobble_error )(sp_session *session, sp_error error)
void(* private_session_mode_changed )(sp_session *session, bool is_private)

Detailed Description

Session callbacks

Registered when you create a session. If some callbacks should not be of interest, set them to NULL.

Examples:

jukebox.c.


Field Documentation

Called when there is a connection error, and the library has problems reconnecting to the Spotify service. Could be called multiple times (as long as the problem is present)

Parameters:
[in] session Session
[in] error One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_CLIENT_TOO_OLD SP_ERROR_UNABLE_TO_CONTACT_SERVER SP_ERROR_BAD_USERNAME_OR_PASSWORD SP_ERROR_USER_BANNED SP_ERROR_USER_NEEDS_PREMIUM SP_ERROR_OTHER_TRANSIENT SP_ERROR_OTHER_PERMANENT

Called when the connection state has updated - such as when logging in, going offline, etc.

Parameters:
[in] session Session
void( * sp_session_callbacks::credentials_blob_updated)(sp_session *session, const char *blob)

Called when storable credentials have been updated, usually called when we have connected to the AP.

Parameters:
[in] session Session
[in] blob Blob is a null-terminated string which contains an encrypted token that can be stored safely on disk instead of storing plaintext passwords.

End of track. Called when the currently played track has reached its end.

Note:
This function is invoked from the main thread
Parameters:
[in] session Session

Called to query application about its audio buffer

Note:
This function is called from an internal session thread - you need to have proper synchronization!
This function must never block.
Parameters:
[in] session Session
[out] stats Stats struct to be filled by application
void( * sp_session_callbacks::log_message)(sp_session *session, const char *data)

Logging callback.

Parameters:
[in] session Session
[in] data Log data

Called when login has been processed and was successful

Parameters:
[in] session Session
[in] error One of the following errors, from sp_error SP_ERROR_OK SP_ERROR_CLIENT_TOO_OLD SP_ERROR_UNABLE_TO_CONTACT_SERVER SP_ERROR_BAD_USERNAME_OR_PASSWORD SP_ERROR_USER_BANNED SP_ERROR_USER_NEEDS_PREMIUM SP_ERROR_OTHER_TRANSIENT SP_ERROR_OTHER_PERMANENT

Called when logout has been processed. Either called explicitly if you initialize a logout operation, or implicitly if there is a permanent connection error

Parameters:
[in] session Session
void( * sp_session_callbacks::message_to_user)(sp_session *session, const char *message)

Called when the access point wants to display a message to the user

In the desktop client, these are shown in a blueish toolbar just below the search box.

Parameters:
[in] session Session
[in] message String in UTF-8 format.

Called whenever metadata has been updated

If you have metadata cached outside of libspotify, you should purge your caches and fetch new versions.

Parameters:
[in] session Session
int( * sp_session_callbacks::music_delivery)(sp_session *session, const sp_audioformat *format, const void *frames, int num_frames)

Called when there is decompressed audio data available.

Parameters:
[in] session Session
[in] format Audio format descriptor sp_audioformat
[in] frames Points to raw PCM data as described by format
[in] num_frames Number of available samples in frames. If this is 0, a discontinuity has occurred (such as after a seek). The application should flush its audio fifos, etc.
Returns:
Number of frames consumed. This value can be used to rate limit the output from the library if your output buffers are saturated. The library will retry delivery in about 100ms.
Note:
This function is called from an internal session thread - you need to have proper synchronization!
This function must never block. If your output buffers are full you must return 0 to signal that the library should retry delivery in a short while.

Called when processing needs to take place on the main thread.

You need to call sp_session_process_events() in the main thread to get libspotify to do more work. Failure to do so may cause request timeouts, or a lost connection.

Parameters:
[in] session Session
Note:
This function is called from an internal session thread - you need to have proper synchronization!

Called when offline synchronization status is updated

Parameters:
[in] session Session
[in] error Offline error. Will be SP_ERROR_OK if the offline synchronization error state has cleared

Called when offline synchronization status is updated

Parameters:
[in] session Session

Music has been paused because an account only allows music to be played from one location simultaneously.

Note:
When this callback is invoked the application should behave just as if the user pressed the pause button. The application should also display a message to the user indicating the playback has been paused because another application is playing using the same account.
IT MUST NOT automatically resume playback but must instead wait for the user to press play.
Parameters:
[in] session Session

Called when there is a change in the private session mode

Parameters:
[in] session Session
[in] isPrivate True if in private session, false otherwhise

Called when there is a scrobble error event

Parameters:
[in] session Session
[in] error Scrobble error. Currently SP_ERROR_LASTFM_AUTH_ERROR.

Called when audio playback should start

Note:
For this to work correctly the application must also implement get_audio_buffer_stats()
This function is called from an internal session thread - you need to have proper synchronization!
This function must never block.
Parameters:
[in] session Session

Called when audio playback should stop

Note:
For this to work correctly the application must also implement get_audio_buffer_stats()
This function is called from an internal session thread - you need to have proper synchronization!
This function must never block.
Parameters:
[in] session Session

Streaming error. Called when streaming cannot start or continue.

Note:
This function is invoked from the main thread
Parameters:
[in] session Session
[in] error One of the following errors, from sp_error SP_ERROR_NO_STREAM_AVAILABLE SP_ERROR_OTHER_TRANSIENT SP_ERROR_OTHER_PERMANENT

Called after user info (anything related to sp_user objects) have been updated.

Parameters:
[in] session Session

The documentation for this struct was generated from the following file:

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/structsp__session__config.html ================================================ libspotify: sp_session_config Struct Reference

sp_session_config Struct Reference
[Session handling]

#include <api.h>

Data Fields

int api_version
 The version of the Spotify API your application is compiled with. Set to SPOTIFY_API_VERSION.
const char * cache_location
const char * settings_location
const void * application_key
 Your application key.
size_t application_key_size
 The size of the application key in bytes.
const char * user_agent
const sp_session_callbackscallbacks
 Delivery callbacks for session events, or NULL if you are not interested in any callbacks (not recommended!).
void * userdata
 User supplied data for your application.
bool compress_playlists
bool dont_save_metadata_for_playlists
bool initially_unload_playlists
const char * device_id
const char * proxy
const char * proxy_username
const char * proxy_password
const char * ca_certs_filename
const char * tracefile

Detailed Description

Session config

Examples:

jukebox.c.


Field Documentation

Path to a file containing the root ca certificates that the peer should be verified with. The file must be a concatenation of all certificates in PEM format. Provided with libspotify is a sample pem file in examples. It is recommended that the application export a similar file from the local certificate store.

The location where Spotify will write cache files. This cache include tracks, cached browse results and coverarts. Set to empty string ("") to disable cache

Compress local copy of playlists, reduces disk space usage

Device ID for offline synchronization and logging purposes. The Device Id must be unique to the particular device instance, i.e. no two units must supply the same Device ID. The Device ID must not change between sessions or power cycles. Good examples is the device's MAC address or unique serial number.

Don't save metadata for local copies of playlists Reduces disk space usage at the expense of needing to request metadata from Spotify backend when loading list

Avoid loading playlists into RAM on startup. See sp_playlist_is_in_ram() for more details.

Url to the proxy server that should be used. The format is protocol://<host>:port (where protocal is http/https/socks4/socks5)

Password to authenticate with proxy server

Username to authenticate with proxy server

The location where Spotify will write setting files and per-user cache items. This includes playlists, track metadata, etc. 'settings_location' may be the same path as 'cache_location'. 'settings_location' folder will not be created (unlike 'cache_location'), if you don't want to create the folder yourself, you can set 'settings_location' to 'cache_location'.

Path to API trace file

"User-Agent" for your application - max 255 characters long The User-Agent should be a relevant, customer facing identification of your application


The documentation for this struct was generated from the following file:

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/structsp__subscribers.html ================================================ libspotify: sp_subscribers Struct Reference

sp_subscribers Struct Reference
[Session handling]

#include <api.h>

Data Fields

unsigned int count
 Number of elements in 'subscribers'.
char * subscribers [1]
 Actual size is 'count'. Array of pointers to canonical usernames.

Detailed Description

List of subscribers returned by sp_playlist_subscribers()


The documentation for this struct was generated from the following file:

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/html/tabs.css ================================================ .tabs, .tabs2, .tabs3 { background-image: url('tab_b.png'); width: 100%; z-index: 101; font-size: 13px; } .tabs2 { font-size: 10px; } .tabs3 { font-size: 9px; } .tablist { margin: 0; padding: 0; display: table; } .tablist li { float: left; display: table-cell; background-image: url('tab_b.png'); line-height: 36px; list-style: none; } .tablist a { display: block; padding: 0 20px; font-weight: bold; background-image:url('tab_s.png'); background-repeat:no-repeat; background-position:right; color: #283A5D; text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); text-decoration: none; outline: none; } .tabs3 .tablist a { padding: 0 10px; } .tablist a:hover { background-image: url('tab_h.png'); background-repeat:repeat-x; color: #fff; text-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); text-decoration: none; } .tablist li.current a { background-image: url('tab_a.png'); background-repeat:repeat-x; color: #fff; text-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); } ================================================ FILE: libspotify/docs/html/toplist_8c-example.html ================================================ libspotify: toplist.c

toplist.c

The toplist.c example shows how you can use toplist functions. It is part of the spshell program

#include <string.h>

#include "spshell.h"
#include "cmd.h"

static void print_album(int index, sp_album *album)
{
    printf("  Album %3d: \"%s\" by \"%s\"\n", index, sp_album_name(album), 
           sp_artist_name(sp_album_artist(album)));
}

static void print_artist(int index, sp_artist *artist)
{
    sp_link *l;
    char url[200];
    printf("  Artist %3d: \"%s\"\n", index, sp_artist_name(artist));

    l = sp_link_create_from_artist_portrait(artist, SP_IMAGE_SIZE_NORMAL);
    if(l != NULL) {
        sp_link_as_string(l, url, sizeof(url));
        printf("    Portrait: %s\n", url);
        sp_link_release(l);
    }
}


static void SP_CALLCONV got_toplist(sp_toplistbrowse *result, void *userdata)
{
    int i;

    // We print from all types. Only one of the loops will acually yield anything.

    for(i = 0; i < sp_toplistbrowse_num_artists(result); i++)
        print_artist(i + 1, sp_toplistbrowse_artist(result, i));

    for(i = 0; i < sp_toplistbrowse_num_albums(result); i++)
        print_album(i + 1, sp_toplistbrowse_album(result, i));

    for(i = 0; i < sp_toplistbrowse_num_tracks(result); i++) {
        printf("%3d: ", i + 1);
        print_track(sp_toplistbrowse_track(result, i));
    }

    sp_toplistbrowse_release(result);
    cmd_done();
}



static void toplist_usage(void)
{
    fprintf(stderr, "Usage: toplist (tracks | albums | artists) (global | region <countrycode> | user)\n");
}

int cmd_toplist(int argc, char **argv)
{
    sp_toplisttype type;
    sp_toplistregion region;

    if(argc < 3) {
        toplist_usage();
        return -1;
    }

    if(!strcasecmp(argv[1], "artists"))
        type = SP_TOPLIST_TYPE_ARTISTS;
    else if(!strcasecmp(argv[1], "albums"))
        type = SP_TOPLIST_TYPE_ALBUMS;
    else if(!strcasecmp(argv[1], "tracks"))
        type = SP_TOPLIST_TYPE_TRACKS;
    else {
        toplist_usage();
        return -1;
    }


    if(!strcasecmp(argv[2], "global"))
        region = SP_TOPLIST_REGION_EVERYWHERE;
    else if(!strcasecmp(argv[2], "user"))
        region = SP_TOPLIST_REGION_USER;
    else if(!strcasecmp(argv[2], "region")) {

        if(argc != 4 || strlen(argv[3]) != 2) {
            toplist_usage();
            return -1;
        }
        region = SP_TOPLIST_REGION(argv[3][0], argv[3][1]);
    } else {
        toplist_usage();
        return -1;
    }

    sp_toplistbrowse_create(g_session, type, region, NULL, got_toplist, NULL);
    return 0;
}

Generated on Wed Jun 13 2012 14:22:40.
Copyright © 2006–2012 Spotify Ltd
================================================ FILE: libspotify/docs/images/spotify-core.txt ================================================ Uses SPOTIFY(R) CORE ================================================ FILE: libspotify/examples/Makefile ================================================ EXAMPLES=jukebox spshell localfiles .PHONY: all clean ifdef LIBSPOTIFY_PATH ARG=LIBSPOTIFY_PATH="$(shell cd "$(LIBSPOTIFY_PATH)" && pwd)" endif all clean: for a in $(EXAMPLES); do $(MAKE) -C $$a $(ARG) $@; done ================================================ FILE: libspotify/examples/Randomify/English.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: libspotify/examples/Randomify/English.lproj/MainMenu.xib ================================================ 1050 10C540 740 1038.25 458.00 com.apple.InterfaceBuilder.CocoaPlugin 740 YES YES com.apple.InterfaceBuilder.CocoaPlugin YES YES YES YES NSApplication FirstResponder NSApplication AMainMenu YES Randomify 1048576 2147483647 NSImage NSMenuCheckmark NSImage NSMenuMixedState submenuAction: Randomify YES About Randomify 2147483647 YES YES 1048576 2147483647 Preferences… , 1048576 2147483647 YES YES 1048576 2147483647 Services 1048576 2147483647 submenuAction: Services YES _NSServicesMenu YES YES 1048576 2147483647 Hide Randomify h 1048576 2147483647 Hide Others h 1572864 2147483647 Show All 1048576 2147483647 YES YES 1048576 2147483647 Quit Randomify q 1048576 2147483647 _NSAppleMenu File 1048576 2147483647 submenuAction: File YES New n 1048576 2147483647 Open… o 1048576 2147483647 Open Recent 1048576 2147483647 submenuAction: Open Recent YES Clear Menu 1048576 2147483647 _NSRecentDocumentsMenu YES YES 1048576 2147483647 Close w 1048576 2147483647 Save s 1048576 2147483647 Save As… S 1179648 2147483647 Revert to Saved 2147483647 YES YES 1048576 2147483647 Page Setup... P 1179648 2147483647 Print… p 1048576 2147483647 Edit 1048576 2147483647 submenuAction: Edit YES Undo z 1048576 2147483647 Redo Z 1179648 2147483647 YES YES 1048576 2147483647 Cut x 1048576 2147483647 Copy c 1048576 2147483647 Paste v 1048576 2147483647 Paste and Match Style V 1572864 2147483647 Delete 1048576 2147483647 Select All a 1048576 2147483647 YES YES 1048576 2147483647 Find 1048576 2147483647 submenuAction: Find YES Find… f 1048576 2147483647 1 Find Next g 1048576 2147483647 2 Find Previous G 1179648 2147483647 3 Use Selection for Find e 1048576 2147483647 7 Jump to Selection j 1048576 2147483647 Spelling and Grammar 1048576 2147483647 submenuAction: Spelling and Grammar YES Show Spelling and Grammar : 1048576 2147483647 Check Document Now ; 1048576 2147483647 YES YES 2147483647 Check Spelling While Typing 1048576 2147483647 Check Grammar With Spelling 1048576 2147483647 Correct Spelling Automatically 2147483647 Substitutions 1048576 2147483647 submenuAction: Substitutions YES Show Substitutions 2147483647 YES YES 2147483647 Smart Copy/Paste f 1048576 2147483647 1 Smart Quotes g 1048576 2147483647 2 Smart Dashes 2147483647 Smart Links G 1179648 2147483647 3 Text Replacement 2147483647 Transformations 2147483647 submenuAction: Transformations YES Make Upper Case 2147483647 Make Lower Case 2147483647 Capitalize 2147483647 Speech 1048576 2147483647 submenuAction: Speech YES Start Speaking 1048576 2147483647 Stop Speaking 1048576 2147483647 Format 2147483647 submenuAction: Format YES Font 2147483647 submenuAction: Font YES Show Fonts t 1048576 2147483647 Bold b 1048576 2147483647 2 Italic i 1048576 2147483647 1 Underline u 1048576 2147483647 YES YES 2147483647 Bigger + 1048576 2147483647 3 Smaller - 1048576 2147483647 4 YES YES 2147483647 Kern 2147483647 submenuAction: Kern YES Use Default 2147483647 Use None 2147483647 Tighten 2147483647 Loosen 2147483647 Ligature 2147483647 submenuAction: Ligature YES Use Default 2147483647 Use None 2147483647 Use All 2147483647 Baseline 2147483647 submenuAction: Baseline YES Use Default 2147483647 Superscript 2147483647 Subscript 2147483647 Raise 2147483647 Lower 2147483647 YES YES 2147483647 Show Colors C 1048576 2147483647 YES YES 2147483647 Copy Style c 1572864 2147483647 Paste Style v 1572864 2147483647 _NSFontMenu Text 2147483647 submenuAction: Text YES Align Left { 1048576 2147483647 Center | 1048576 2147483647 Justify 2147483647 Align Right } 1048576 2147483647 YES YES 2147483647 Writing Direction 2147483647 submenuAction: Writing Direction YES YES Paragraph 2147483647 CURlZmF1bHQ 2147483647 CUxlZnQgdG8gUmlnaHQ 2147483647 CVJpZ2h0IHRvIExlZnQ 2147483647 YES YES 2147483647 YES Selection 2147483647 CURlZmF1bHQ 2147483647 CUxlZnQgdG8gUmlnaHQ 2147483647 CVJpZ2h0IHRvIExlZnQ 2147483647 YES YES 2147483647 Show Ruler 2147483647 Copy Ruler c 1310720 2147483647 Paste Ruler v 1310720 2147483647 View 1048576 2147483647 submenuAction: View YES Show Toolbar t 1572864 2147483647 Customize Toolbar… 1048576 2147483647 Window 1048576 2147483647 submenuAction: Window YES Minimize m 1048576 2147483647 Zoom 1048576 2147483647 YES YES 1048576 2147483647 Bring All to Front 1048576 2147483647 _NSWindowsMenu Help 2147483647 submenuAction: Help YES Randomify Help ? 1048576 2147483647 _NSHelpMenu _NSMainMenu 15 2 {{335, 428}, {497, 322}} 1954021376 Randomify NSWindow {1.79769e+308, 1.79769e+308} 256 YES 292 {{20, 20}, {158, 22}} YES -1804468671 272630784 LucidaGrande 13 1044 Username YES 6 System textBackgroundColor 3 MQA 6 System textColor 3 MAA 288 {{182, 20}, {158, 22}} YES 343014976 272630784 Password YES YES NSAllRomanInputSourcesLocaleIdentifier 297 {{366, 13}, {117, 32}} YES 67239424 134217728 Login & Play -2038284033 129 DQ 200 25 274 {{20, 66}, {457, 216}} YES -2073952767 272629760 YES 265 {{372, 289}, {48, 26}} YES -2080244224 134217728 STHeitiSC-Light 12 16 -2033434369 162 400 75 265 {{429, 290}, {48, 25}} YES -2080244224 134217728 HiraKakuProN-W3 32 16 -2033434369 162 400 75 292 {{17, 44}, {164, 14}} YES 68288064 272761856 Username LucidaGrande 11 3100 6 System controlColor 3 MC42NjY2NjY2NjY3AA 6 System controlTextColor 292 {{179, 44}, {164, 14}} YES 68288064 272761856 Password 266 {{17, 291}, {340, 18}} YES 67239488 272631808 Playing x by y LucidaGrande-Bold 14 16 {497, 322} {{0, 0}, {1920, 1178}} {1.79769e+308, 1.79769e+308} RandomifyAppDelegate NSFontManager YES username password YES YES performMiniaturize: 37 arrangeInFront: 39 print: 86 runPageLayout: 87 clearRecentDocuments: 127 orderFrontStandardAboutPanel: 142 performClose: 193 toggleContinuousSpellChecking: 222 undo: 223 copy: 224 checkSpelling: 225 paste: 226 stopSpeaking: 227 cut: 228 showGuessPanel: 230 redo: 231 selectAll: 232 startSpeaking: 233 delete: 235 performZoom: 240 performFindPanelAction: 241 centerSelectionInVisibleArea: 245 toggleGrammarChecking: 347 toggleSmartInsertDelete: 355 toggleAutomaticQuoteSubstitution: 356 toggleAutomaticLinkDetection: 357 saveDocument: 362 saveDocumentAs: 363 revertDocumentToSaved: 364 runToolbarCustomizationPalette: 365 toggleToolbarShown: 366 hide: 367 hideOtherApplications: 368 unhideAllApplications: 370 newDocument: 373 openDocument: 374 addFontTrait: 421 addFontTrait: 422 modifyFont: 423 orderFrontFontPanel: 424 modifyFont: 425 raiseBaseline: 426 lowerBaseline: 427 copyFont: 428 subscript: 429 superscript: 430 tightenKerning: 431 underline: 432 orderFrontColorPanel: 433 useAllLigatures: 434 loosenKerning: 435 pasteFont: 436 unscript: 437 useStandardKerning: 438 useStandardLigatures: 439 turnOffLigatures: 440 turnOffKerning: 441 terminate: 449 toggleAutomaticSpellingCorrection: 456 orderFrontSubstitutionsPanel: 458 toggleAutomaticDashSubstitution: 461 toggleAutomaticTextReplacement: 463 uppercaseWord: 464 capitalizeWord: 467 lowercaseWord: 468 pasteAsPlainText: 486 performFindPanelAction: 487 performFindPanelAction: 488 performFindPanelAction: 489 showHelp: 493 delegate 495 alignCenter: 518 pasteRuler: 519 toggleRuler: 520 alignRight: 521 copyRuler: 522 alignJustified: 523 alignLeft: 524 makeBaseWritingDirectionNatural: 525 makeBaseWritingDirectionLeftToRight: 526 makeBaseWritingDirectionRightToLeft: 527 makeTextWritingDirectionNatural: 528 makeTextWritingDirectionLeftToRight: 529 makeTextWritingDirectionRightToLeft: 530 window 532 password 539 username 540 loginAndPlay: 541 log 544 value: values.username value: values.username value values.username 2 547 value: values.password value: values.password value values.password 2 549 togglePlay: 554 playARandomSong: 555 playButton 556 status 563 YES 0 -2 File's Owner -1 First Responder -3 Application 29 YES 19 YES 56 YES 217 YES 83 YES 81 YES 75 80 78 72 82 124 YES 77 73 79 112 74 125 YES 126 205 YES 202 198 207 214 199 203 197 206 215 218 YES 216 YES 200 YES 219 201 204 220 YES 213 210 221 208 209 57 YES 58 134 150 136 144 129 143 236 131 YES 149 145 130 24 YES 92 5 239 23 295 YES 296 YES 297 298 211 YES 212 YES 195 196 346 348 YES 349 YES 350 351 354 371 YES 372 YES 375 YES 376 YES 377 YES 388 YES 389 390 391 392 393 394 395 396 397 YES 398 YES 399 YES 400 401 402 403 404 405 YES 406 407 408 409 410 411 YES 412 413 414 415 YES 416 417 418 419 420 450 YES 451 YES 452 453 454 457 459 460 462 465 466 485 490 YES 491 YES 492 494 496 YES 497 YES 498 499 500 501 502 503 YES 504 505 506 507 508 YES 509 510 511 512 513 514 515 516 517 533 YES 534 535 YES 536 537 YES 538 542 YES 543 545 550 YES 551 552 YES 553 557 YES 558 559 YES 560 561 YES 562 YES YES -3.IBPluginDependency 112.IBPluginDependency 112.ImportedFromIB2 124.IBPluginDependency 124.ImportedFromIB2 125.IBPluginDependency 125.ImportedFromIB2 125.editorWindowContentRectSynchronizationRect 126.IBPluginDependency 126.ImportedFromIB2 129.IBPluginDependency 129.ImportedFromIB2 130.IBPluginDependency 130.ImportedFromIB2 130.editorWindowContentRectSynchronizationRect 131.IBPluginDependency 131.ImportedFromIB2 134.IBPluginDependency 134.ImportedFromIB2 136.IBPluginDependency 136.ImportedFromIB2 143.IBPluginDependency 143.ImportedFromIB2 144.IBPluginDependency 144.ImportedFromIB2 145.IBPluginDependency 145.ImportedFromIB2 149.IBPluginDependency 149.ImportedFromIB2 150.IBPluginDependency 150.ImportedFromIB2 19.IBPluginDependency 19.ImportedFromIB2 195.IBPluginDependency 195.ImportedFromIB2 196.IBPluginDependency 196.ImportedFromIB2 197.IBPluginDependency 197.ImportedFromIB2 198.IBPluginDependency 198.ImportedFromIB2 199.IBPluginDependency 199.ImportedFromIB2 200.IBEditorWindowLastContentRect 200.IBPluginDependency 200.ImportedFromIB2 200.editorWindowContentRectSynchronizationRect 201.IBPluginDependency 201.ImportedFromIB2 202.IBPluginDependency 202.ImportedFromIB2 203.IBPluginDependency 203.ImportedFromIB2 204.IBPluginDependency 204.ImportedFromIB2 205.IBEditorWindowLastContentRect 205.IBPluginDependency 205.ImportedFromIB2 205.editorWindowContentRectSynchronizationRect 206.IBPluginDependency 206.ImportedFromIB2 207.IBPluginDependency 207.ImportedFromIB2 208.IBPluginDependency 208.ImportedFromIB2 209.IBPluginDependency 209.ImportedFromIB2 210.IBPluginDependency 210.ImportedFromIB2 211.IBPluginDependency 211.ImportedFromIB2 212.IBPluginDependency 212.ImportedFromIB2 212.editorWindowContentRectSynchronizationRect 213.IBPluginDependency 213.ImportedFromIB2 214.IBPluginDependency 214.ImportedFromIB2 215.IBPluginDependency 215.ImportedFromIB2 216.IBPluginDependency 216.ImportedFromIB2 217.IBPluginDependency 217.ImportedFromIB2 218.IBPluginDependency 218.ImportedFromIB2 219.IBPluginDependency 219.ImportedFromIB2 220.IBEditorWindowLastContentRect 220.IBPluginDependency 220.ImportedFromIB2 220.editorWindowContentRectSynchronizationRect 221.IBPluginDependency 221.ImportedFromIB2 23.IBPluginDependency 23.ImportedFromIB2 236.IBPluginDependency 236.ImportedFromIB2 239.IBPluginDependency 239.ImportedFromIB2 24.IBEditorWindowLastContentRect 24.IBPluginDependency 24.ImportedFromIB2 24.editorWindowContentRectSynchronizationRect 29.IBEditorWindowLastContentRect 29.IBPluginDependency 29.ImportedFromIB2 29.WindowOrigin 29.editorWindowContentRectSynchronizationRect 295.IBPluginDependency 296.IBEditorWindowLastContentRect 296.IBPluginDependency 296.editorWindowContentRectSynchronizationRect 297.IBPluginDependency 298.IBPluginDependency 346.IBPluginDependency 346.ImportedFromIB2 348.IBPluginDependency 348.ImportedFromIB2 349.IBEditorWindowLastContentRect 349.IBPluginDependency 349.ImportedFromIB2 349.editorWindowContentRectSynchronizationRect 350.IBPluginDependency 350.ImportedFromIB2 351.IBPluginDependency 351.ImportedFromIB2 354.IBPluginDependency 354.ImportedFromIB2 371.IBEditorWindowLastContentRect 371.IBPluginDependency 371.IBWindowTemplateEditedContentRect 371.NSWindowTemplate.visibleAtLaunch 371.editorWindowContentRectSynchronizationRect 371.windowTemplate.hasMaxSize 371.windowTemplate.maxSize 372.IBPluginDependency 375.IBPluginDependency 376.IBEditorWindowLastContentRect 376.IBPluginDependency 377.IBPluginDependency 388.IBEditorWindowLastContentRect 388.IBPluginDependency 389.IBPluginDependency 390.IBPluginDependency 391.IBPluginDependency 392.IBPluginDependency 393.IBPluginDependency 394.IBPluginDependency 395.IBPluginDependency 396.IBPluginDependency 397.IBPluginDependency 398.IBPluginDependency 399.IBPluginDependency 400.IBPluginDependency 401.IBPluginDependency 402.IBPluginDependency 403.IBPluginDependency 404.IBPluginDependency 405.IBPluginDependency 406.IBPluginDependency 407.IBPluginDependency 408.IBPluginDependency 409.IBPluginDependency 410.IBPluginDependency 411.IBPluginDependency 412.IBPluginDependency 413.IBPluginDependency 414.IBPluginDependency 415.IBPluginDependency 416.IBPluginDependency 417.IBPluginDependency 418.IBPluginDependency 419.IBPluginDependency 450.IBPluginDependency 451.IBEditorWindowLastContentRect 451.IBPluginDependency 452.IBPluginDependency 453.IBPluginDependency 454.IBPluginDependency 457.IBPluginDependency 459.IBPluginDependency 460.IBPluginDependency 462.IBPluginDependency 465.IBPluginDependency 466.IBPluginDependency 485.IBPluginDependency 490.IBPluginDependency 491.IBEditorWindowLastContentRect 491.IBPluginDependency 492.IBPluginDependency 496.IBPluginDependency 497.IBEditorWindowLastContentRect 497.IBPluginDependency 498.IBPluginDependency 499.IBPluginDependency 5.IBPluginDependency 5.ImportedFromIB2 500.IBPluginDependency 501.IBPluginDependency 502.IBPluginDependency 503.IBPluginDependency 504.IBPluginDependency 505.IBPluginDependency 506.IBPluginDependency 507.IBPluginDependency 508.IBEditorWindowLastContentRect 508.IBPluginDependency 509.IBPluginDependency 510.IBPluginDependency 511.IBPluginDependency 512.IBPluginDependency 513.IBPluginDependency 514.IBPluginDependency 515.IBPluginDependency 516.IBPluginDependency 517.IBPluginDependency 533.IBPluginDependency 534.IBPluginDependency 535.IBPluginDependency 536.IBPluginDependency 537.IBPluginDependency 538.IBPluginDependency 542.IBPluginDependency 543.IBPluginDependency 550.IBPluginDependency 551.IBPluginDependency 552.IBPluginDependency 553.IBPluginDependency 557.IBPluginDependency 558.IBPluginDependency 559.IBPluginDependency 56.IBPluginDependency 56.ImportedFromIB2 560.IBPluginDependency 561.IBPluginDependency 562.IBPluginDependency 57.IBEditorWindowLastContentRect 57.IBPluginDependency 57.ImportedFromIB2 57.editorWindowContentRectSynchronizationRect 58.IBPluginDependency 58.ImportedFromIB2 72.IBPluginDependency 72.ImportedFromIB2 73.IBPluginDependency 73.ImportedFromIB2 74.IBPluginDependency 74.ImportedFromIB2 75.IBPluginDependency 75.ImportedFromIB2 77.IBPluginDependency 77.ImportedFromIB2 78.IBPluginDependency 78.ImportedFromIB2 79.IBPluginDependency 79.ImportedFromIB2 80.IBPluginDependency 80.ImportedFromIB2 81.IBEditorWindowLastContentRect 81.IBPluginDependency 81.ImportedFromIB2 81.editorWindowContentRectSynchronizationRect 82.IBPluginDependency 82.ImportedFromIB2 83.IBPluginDependency 83.ImportedFromIB2 92.IBPluginDependency 92.ImportedFromIB2 YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{522, 812}, {146, 23}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{436, 809}, {64, 6}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{753, 187}, {275, 113}} com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {275, 83}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{547, 180}, {254, 283}} com.apple.InterfaceBuilder.CocoaPlugin {{187, 434}, {243, 243}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {167, 43}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{753, 217}, {238, 103}} com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {241, 103}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{654, 239}, {194, 73}} com.apple.InterfaceBuilder.CocoaPlugin {{525, 802}, {197, 73}} {{380, 836}, {442, 20}} com.apple.InterfaceBuilder.CocoaPlugin {74, 862} {{6, 978}, {478, 20}} com.apple.InterfaceBuilder.CocoaPlugin {{604, 269}, {231, 43}} com.apple.InterfaceBuilder.CocoaPlugin {{475, 832}, {234, 43}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{746, 287}, {220, 133}} com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {215, 63}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{183, 462}, {497, 322}} com.apple.InterfaceBuilder.CocoaPlugin {{183, 462}, {497, 322}} {{33, 99}, {480, 360}} {9999, 122} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{591, 420}, {83, 43}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{523, 2}, {178, 283}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{753, 197}, {170, 63}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{725, 289}, {246, 23}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{674, 260}, {204, 183}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{878, 180}, {164, 173}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{286, 129}, {275, 183}} com.apple.InterfaceBuilder.CocoaPlugin {{23, 794}, {245, 183}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{452, 109}, {196, 203}} com.apple.InterfaceBuilder.CocoaPlugin {{145, 474}, {199, 203}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin YES YES YES YES 563 YES RandomifyAppDelegate NSObject YES YES loginAndPlay: playARandomSong: togglePlay: YES id id id YES YES log password playButton status username window YES NSTextField NSTextField NSButton NSTextField NSTextField NSWindow IBProjectSource RandomifyAppDelegate.h 0 com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 YES ../Randomify.xcodeproj 3 ================================================ FILE: libspotify/examples/Randomify/Randomify-Info.plist ================================================ CFBundleDevelopmentRegion English CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier com.spotify.randomify CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: libspotify/examples/Randomify/Randomify.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 45; objects = { /* Begin PBXBuildFile section */ 0575227B1161F3A5003FFC9D /* libspotify.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0575227A1161F3A5003FFC9D /* libspotify.framework */; }; 057522801161F3C9003FFC9D /* libspotify.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 0575227A1161F3A5003FFC9D /* libspotify.framework */; }; 057522901161F4AE003FFC9D /* appkey.c in Sources */ = {isa = PBXBuildFile; fileRef = 0575228F1161F4AE003FFC9D /* appkey.c */; }; 057522BB1161F629003FFC9D /* audio.c in Sources */ = {isa = PBXBuildFile; fileRef = 057522BA1161F629003FFC9D /* audio.c */; }; 057522C01161F6F7003FFC9D /* SpotifySession.m in Sources */ = {isa = PBXBuildFile; fileRef = 057522BF1161F6F7003FFC9D /* SpotifySession.m */; }; 05752469116225C4003FFC9D /* SpotifyPlaylist.m in Sources */ = {isa = PBXBuildFile; fileRef = 05752468116225C4003FFC9D /* SpotifyPlaylist.m */; }; 057524E11162303B003FFC9D /* SpotifyTrack.m in Sources */ = {isa = PBXBuildFile; fileRef = 057524E01162303B003FFC9D /* SpotifyTrack.m */; }; 057526DD116248B1003FFC9D /* osx-audio.c in Sources */ = {isa = PBXBuildFile; fileRef = 057526DC116248B1003FFC9D /* osx-audio.c */; }; 057526E1116248D4003FFC9D /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 057526E0116248D4003FFC9D /* AudioToolbox.framework */; }; 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; 256AC3DA0F4B6AC300CF3369 /* RandomifyAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 256AC3D90F4B6AC300CF3369 /* RandomifyAppDelegate.m */; }; 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 0575226B1161F20D003FFC9D /* Copy Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 057522801161F3C9003FFC9D /* libspotify.framework in Copy Frameworks */, ); name = "Copy Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0575227A1161F3A5003FFC9D /* libspotify.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = libspotify.framework; path = ../../libspotify.framework; sourceTree = SOURCE_ROOT; }; 0575228F1161F4AE003FFC9D /* appkey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = appkey.c; path = ../appkey.c; sourceTree = SOURCE_ROOT; }; 057522B01161F609003FFC9D /* audio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio.h; path = ../jukebox/audio.h; sourceTree = SOURCE_ROOT; }; 057522B21161F609003FFC9D /* queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = queue.h; path = ../jukebox/queue.h; sourceTree = SOURCE_ROOT; }; 057522BA1161F629003FFC9D /* audio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = audio.c; path = ../jukebox/audio.c; sourceTree = SOURCE_ROOT; }; 057522BE1161F6F7003FFC9D /* SpotifySession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpotifySession.h; sourceTree = ""; }; 057522BF1161F6F7003FFC9D /* SpotifySession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpotifySession.m; sourceTree = ""; }; 05752467116225C4003FFC9D /* SpotifyPlaylist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpotifyPlaylist.h; sourceTree = ""; }; 05752468116225C4003FFC9D /* SpotifyPlaylist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpotifyPlaylist.m; sourceTree = ""; }; 057524DF1162303B003FFC9D /* SpotifyTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpotifyTrack.h; sourceTree = ""; }; 057524E01162303B003FFC9D /* SpotifyTrack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpotifyTrack.m; sourceTree = ""; }; 057526DC116248B1003FFC9D /* osx-audio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "osx-audio.c"; path = "../jukebox/osx-audio.c"; sourceTree = SOURCE_ROOT; }; 057526E0116248D4003FFC9D /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 1DDD58150DA1D0A300B32029 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; 256AC3D80F4B6AC300CF3369 /* RandomifyAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomifyAppDelegate.h; sourceTree = ""; }; 256AC3D90F4B6AC300CF3369 /* RandomifyAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RandomifyAppDelegate.m; sourceTree = ""; }; 256AC3F00F4B6AF500CF3369 /* Randomify_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Randomify_Prefix.pch; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Randomify-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Randomify-Info.plist"; sourceTree = ""; }; 8D1107320486CEB800E47090 /* Randomify.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Randomify.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8D11072E0486CEB800E47090 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 0575227B1161F3A5003FFC9D /* libspotify.framework in Frameworks */, 057526E1116248D4003FFC9D /* AudioToolbox.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 080E96DDFE201D6D7F000001 /* Classes */ = { isa = PBXGroup; children = ( 256AC3D80F4B6AC300CF3369 /* RandomifyAppDelegate.h */, 256AC3D90F4B6AC300CF3369 /* RandomifyAppDelegate.m */, 057522BE1161F6F7003FFC9D /* SpotifySession.h */, 057522BF1161F6F7003FFC9D /* SpotifySession.m */, 05752467116225C4003FFC9D /* SpotifyPlaylist.h */, 05752468116225C4003FFC9D /* SpotifyPlaylist.m */, 057524DF1162303B003FFC9D /* SpotifyTrack.h */, 057524E01162303B003FFC9D /* SpotifyTrack.m */, ); name = Classes; sourceTree = ""; }; 19C28FACFE9D520D11CA2CBB /* Products */ = { isa = PBXGroup; children = ( 8D1107320486CEB800E47090 /* Randomify.app */, ); name = Products; sourceTree = ""; }; 29B97314FDCFA39411CA2CEA /* Randomify */ = { isa = PBXGroup; children = ( 080E96DDFE201D6D7F000001 /* Classes */, 29B97315FDCFA39411CA2CEA /* Other Sources */, 29B97317FDCFA39411CA2CEA /* Resources */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); name = Randomify; sourceTree = ""; }; 29B97315FDCFA39411CA2CEA /* Other Sources */ = { isa = PBXGroup; children = ( 057522B01161F609003FFC9D /* audio.h */, 057522B21161F609003FFC9D /* queue.h */, 057526DC116248B1003FFC9D /* osx-audio.c */, 0575228F1161F4AE003FFC9D /* appkey.c */, 057522BA1161F629003FFC9D /* audio.c */, 256AC3F00F4B6AF500CF3369 /* Randomify_Prefix.pch */, 29B97316FDCFA39411CA2CEA /* main.m */, ); name = "Other Sources"; sourceTree = ""; }; 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( 8D1107310486CEB800E47090 /* Randomify-Info.plist */, 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, ); name = Resources; sourceTree = ""; }; 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( 0575227A1161F3A5003FFC9D /* libspotify.framework */, 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 057526E0116248D4003FFC9D /* AudioToolbox.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 8D1107260486CEB800E47090 /* Randomify */ = { isa = PBXNativeTarget; buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Randomify" */; buildPhases = ( 0575226B1161F20D003FFC9D /* Copy Frameworks */, 8D1107290486CEB800E47090 /* Resources */, 8D11072C0486CEB800E47090 /* Sources */, 8D11072E0486CEB800E47090 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Randomify; productInstallPath = "$(HOME)/Applications"; productName = Randomify; productReference = 8D1107320486CEB800E47090 /* Randomify.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Randomify" */; compatibilityVersion = "Xcode 3.1"; hasScannedForEncodings = 1; mainGroup = 29B97314FDCFA39411CA2CEA /* Randomify */; projectDirPath = ""; projectRoot = ""; targets = ( 8D1107260486CEB800E47090 /* Randomify */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 8D1107290486CEB800E47090 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 8D11072C0486CEB800E47090 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 8D11072D0486CEB800E47090 /* main.m in Sources */, 256AC3DA0F4B6AC300CF3369 /* RandomifyAppDelegate.m in Sources */, 057522901161F4AE003FFC9D /* appkey.c in Sources */, 057522BB1161F629003FFC9D /* audio.c in Sources */, 057522C01161F6F7003FFC9D /* SpotifySession.m in Sources */, 05752469116225C4003FFC9D /* SpotifyPlaylist.m in Sources */, 057524E11162303B003FFC9D /* SpotifyTrack.m in Sources */, 057526DD116248B1003FFC9D /* osx-audio.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 089C165DFE840E0CC02AAC07 /* English */, ); name = InfoPlist.strings; sourceTree = ""; }; 1DDD58140DA1D0A300B32029 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 1DDD58150DA1D0A300B32029 /* English */, ); name = MainMenu.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/../..\"", ); GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Randomify_Prefix.pch; INFOPLIST_FILE = "Randomify-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; PRODUCT_NAME = Randomify; }; name = Debug; }; C01FCF4C08A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/../..\"", ); GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Randomify_Prefix.pch; INFOPLIST_FILE = "Randomify-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; PRODUCT_NAME = Randomify; }; name = Release; }; C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; ONLY_ACTIVE_ARCH = YES; PREBINDING = NO; SDKROOT = macosx10.6; }; name = Debug; }; C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PREBINDING = NO; SDKROOT = macosx10.6; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Randomify" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4B08A954540054247B /* Debug */, C01FCF4C08A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Randomify" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4F08A954540054247B /* Debug */, C01FCF5008A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } ================================================ FILE: libspotify/examples/Randomify/RandomifyAppDelegate.h ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * * This example application show how to use libspotify in a Mac OS X * application, playing randomly from your Starred playlist. * * This file is part of the libspotify examples suite. */ #import #import "SpotifySession.h" @interface RandomifyAppDelegate : NSObject { IBOutlet NSWindow *window; IBOutlet NSTextField *username; IBOutlet NSTextField *password; IBOutlet NSTextField *log; IBOutlet NSTextField *status; SpotifySession *session; SpotifyPlaylist *starred; BOOL playing; IBOutlet NSButton *playButton; SpotifyTrack *playingTrack; } -(IBAction)loginAndPlay:(id)sender; -(IBAction)playARandomSong:(id)sender; -(IBAction)togglePlay:(id)sender; @end ================================================ FILE: libspotify/examples/Randomify/RandomifyAppDelegate.m ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * This file is part of the libspotify examples suite. * See RandomifyAppDelegate.h for license. */ #import "RandomifyAppDelegate.h" #import "SpotifySession.h" @interface RandomifyAppDelegate () @property (retain) SpotifyPlaylist *starred; @property (retain) SpotifyTrack *playingTrack; @property BOOL playing; @end @implementation RandomifyAppDelegate @synthesize starred, playingTrack; #pragma mark Session delegates -(void)session:(SpotifySession*)session_ logged:(NSString*)logmsg; { NSString *new = [logmsg stringByAppendingString:log.stringValue]; log.stringValue = new; } -(void)sessionLoggedIn:(SpotifySession*)session_ error:(NSError*)err; { if(err) { [NSApp presentError:err]; return; } [self session:session logged:@"Logged in!\n"]; self.starred = session.starred; starred.delegate = self; [self playARandomSong:nil]; } -(void)session:(SpotifySession*)session_ connectionError:(NSError*)error; { [NSApp presentError:error]; } -(void)session:(SpotifySession*)session_ streamingError:(NSError*)error; { [NSApp presentError:error]; } -(void)playlistEndedUpdating:(SpotifyPlaylist*)playlist; { if(playlist.loaded && !playing) [self playARandomSong:nil]; } -(void)playlistChangedState:(SpotifyPlaylist*)playlist; { if(playlist.loaded && !playing) [self playARandomSong:nil]; } -(void)sessionUpdatedMetadata:(SpotifySession *)session_; { // A track we're waiting for metadata for might now have gotten it. if(!playing && playingTrack && playingTrack.loaded) { if(!playingTrack.available) { [self playARandomSong:nil]; return; } [self session:session logged:[NSString stringWithFormat:@"Playing now loaded track: %@\n", playingTrack]]; status.stringValue = playingTrack.description; [session loadTrack:playingTrack]; [session play]; self.playing = YES; } } -(void)sessionEndedPlayingTrack:(SpotifySession*)session_; { self.playing = NO; [self session:session logged:@"Playlist is ready!\n"]; [self playARandomSong:nil]; } -(void)sessionLostPlayToken:(SpotifySession *)session; { NSRunAlertPanel(@"Lost play token", @"Another client is playing.", @"Arrrghh", nil, nil); self.playing = NO; } #pragma mark - - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { srandom(time(NULL)); self.playing = NO; NSError *err = nil; session = [[SpotifySession alloc] initError:&err]; session.delegate = self; status.stringValue = @"Randomify"; if(!session) { [NSApp presentError:err]; [NSApp terminate:nil]; } } -(IBAction)loginAndPlay:(id)sender; { [session loginUser:username.stringValue password:password.stringValue]; } -(IBAction)playARandomSong:(id)sender; { NSUInteger count = [starred countOfTracks]; if(!starred.loaded || !count) { [self session:session logged:@"Waiting for playlist to load...\n"]; return; } NSUInteger randomIndex = random()%count; self.playingTrack = [starred objectInTracksAtIndex:randomIndex]; if(!self.playingTrack.loaded) { [self session:session logged:@"Waiting for track to load...\n"]; status.stringValue = @"Loading…"; return; } [self session:session logged:[NSString stringWithFormat:@"Playing track: %@\n", playingTrack]]; status.stringValue = playingTrack.description; [session loadTrack:playingTrack]; [session play]; self.playing = YES; } -(IBAction)togglePlay:(id)sender; { if(playing) { [session pause]; self.playing = NO; } else { [session play]; self.playing = YES; } } -(BOOL)playing; { return playing; } -(void)setPlaying:(BOOL)playing_; { [playButton setEnabled:playingTrack != nil]; playing = playing_; [playButton setStringValue:playing ? @"║" : @"▹"]; } @end ================================================ FILE: libspotify/examples/Randomify/SpotifyPlaylist.h ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * This file is part of the libspotify examples suite. * See RandomifyAppDelegate.h for license. */ #import #import #import "SpotifyTrack.h" @class SpotifyPlaylist; @protocol SpotifyPlaylistDelegate @optional -(void)playlistBeganUpdating:(SpotifyPlaylist*)playlist; -(void)playlistEndedUpdating:(SpotifyPlaylist*)playlist; -(void)playlistChangedState:(SpotifyPlaylist*)playlist; @end @interface SpotifyPlaylist : NSObject { sp_playlist *playlist; sp_playlist_callbacks callbacks; sp_session *session; NSObject *delegate; } -(id)initWithPlaylist:(sp_playlist*)playlist_ onSession:(sp_session*)sess; @property (assign) NSObject *delegate; @property (readonly) BOOL loaded; // listen to playlistEndedUpdating if false -(NSUInteger)countOfTracks; -(SpotifyTrack*)objectInTracksAtIndex:(NSUInteger)index; @end ================================================ FILE: libspotify/examples/Randomify/SpotifyPlaylist.m ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * This file is part of the libspotify examples suite. * See RandomifyAppDelegate.h for license. */ #import "SpotifyPlaylist.h" #import "SpotifyTrack.h" #define plobj ((SpotifyPlaylist*)userdata) static void playlist_update_in_progress(sp_playlist *pl, bool done, void *userdata) { if(!done) { if([plobj.delegate respondsToSelector:@selector(playlistBeganUpdating:)]) [plobj.delegate playlistBeganUpdating:plobj]; } else { if([plobj.delegate respondsToSelector:@selector(playlistEndedUpdating:)]) [plobj.delegate playlistEndedUpdating:plobj]; } } static void playlist_state_changed(sp_playlist *pl, void *userdata) { if([plobj.delegate respondsToSelector:@selector(playlistChangedState:)]) [plobj.delegate playlistChangedState:plobj]; } // TODO: Instead of exposing playlist content changes as delegate methods, // make tracks KVO-compliant @implementation SpotifyPlaylist @synthesize delegate; -(id)initWithPlaylist:(sp_playlist*)playlist_ onSession:(sp_session*)sess; { playlist = playlist_; session = sess; callbacks = (sp_playlist_callbacks) { .playlist_update_in_progress = playlist_update_in_progress, }; sp_playlist_add_ref(playlist); sp_playlist_add_callbacks(playlist, &callbacks, self); return self; } -(void)dealloc; { sp_playlist_remove_callbacks(playlist, &callbacks, self); sp_playlist_release(playlist); [super dealloc]; } -(BOOL)loaded; { return sp_playlist_is_loaded(playlist); } -(NSUInteger)countOfTracks; { return sp_playlist_num_tracks(playlist); } -(SpotifyTrack*)objectInTracksAtIndex:(NSUInteger)index; { return [[[SpotifyTrack alloc] initWithTrack:sp_playlist_track(playlist, index) onSession:session] autorelease]; } @end ================================================ FILE: libspotify/examples/Randomify/SpotifySession.h ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * This file is part of the libspotify examples suite. * See RandomifyAppDelegate.h for license. */ #import #import #import "audio.h" #import "SpotifyPlaylist.h" @class SpotifySession; @protocol SpotifySessionDelegate @optional -(void)sessionLoggedIn:(SpotifySession*)session error:(NSError*)err; -(void)sessionLoggedOut:(SpotifySession*)session; -(void)sessionUpdatedMetadata:(SpotifySession*)session; -(void)session:(SpotifySession*)session connectionError:(NSError*)error; -(void)session:(SpotifySession*)session hasMessageToUser:(NSString*)message; -(void)sessionLostPlayToken:(SpotifySession*)session; -(void)session:(SpotifySession*)session logged:(NSString*)logmsg; -(void)sessionEndedPlayingTrack:(SpotifySession*)session; -(void)session:(SpotifySession*)session streamingError:(NSError*)error; -(void)sessionUpdatedUserinfo:(SpotifySession*)session; @end @interface SpotifySession : NSObject { sp_session_config config; sp_session_callbacks callbacks; NSString *cachesDir, *supportDir; audio_fifo_t audiofifo; sp_session *session; NSObject *delegate; } @property (assign) NSObject *delegate; -(id)initError:(NSError**)err; -(void)loginUser:(NSString*)user password:(NSString*)passwd; -(void)logout; @property (readonly) SpotifyPlaylist *starred; // Should be its own class: SpotifyPlayer -(void)loadTrack:(SpotifyTrack*)track; -(void)pause; -(void)play; // Stops processing events. If you only release this object, // the runtime will still own it. -(void)shutdown; @end ================================================ FILE: libspotify/examples/Randomify/SpotifySession.m ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * This file is part of the libspotify examples suite. * See RandomifyAppDelegate.h for license. */ #import "SpotifySession.h" /// The application key is specific to each project, and allows Spotify /// to produce statistics on how our service is used. extern const uint8_t g_appkey[]; /// The size of the application key. extern const size_t g_appkey_size; NSError *makeError(sp_error code) { NSError *err = [NSError errorWithDomain:@"com.spotify" code:code userInfo:[NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithUTF8String:sp_error_message(code)], NSLocalizedDescriptionKey, nil ]]; return err; } @interface SpotifySession () -(void)processEvents; @property (readonly) audio_fifo_t *audiofifo; -(void)tellDelegateThatSessionLogged:(NSString*)msg; @end #pragma mark #pragma mark Callbacks #pragma mark - static SpotifySession *sessobj(sp_session *session) { return (SpotifySession*)sp_session_userdata(session); } static void logged_in(sp_session *session, sp_error error) { NSObject *delegate = sessobj(session).delegate; NSError *err = SP_ERROR_OK == error ? nil : makeError(error); if([delegate respondsToSelector:@selector(sessionLoggedIn:error:)]) [delegate sessionLoggedIn:sessobj(session) error:err]; } static void logged_out(sp_session *session) { NSObject *delegate = sessobj(session).delegate; if([delegate respondsToSelector:@selector(sessionLoggedOut:)]) [delegate sessionLoggedOut:sessobj(session)]; } static void metadata_updated(sp_session *session) { NSObject *delegate = sessobj(session).delegate; if([delegate respondsToSelector:@selector(sessionUpdatedMetadata:)]) [delegate sessionUpdatedMetadata:sessobj(session)]; } static void connection_error(sp_session *session, sp_error error) { NSObject *delegate = sessobj(session).delegate; NSError *err = SP_ERROR_OK == error ? nil : makeError(error); if([delegate respondsToSelector:@selector(session:connectionError:)]) [delegate session:sessobj(session) connectionError:err]; } static void message_to_user(sp_session *session, const char *message) { NSObject *delegate = sessobj(session).delegate; NSString *msg = [NSString stringWithUTF8String:message]; if([delegate respondsToSelector:@selector(session:hasMessageToUser:)]) [delegate session:sessobj(session) hasMessageToUser:msg]; } static void notify_main_thread(sp_session *session) { [sessobj(session) performSelectorOnMainThread:@selector(processEvents) withObject:nil waitUntilDone:NO]; } static int music_delivery(sp_session *sess, const sp_audioformat *format, const void *frames, int num_frames) { audio_fifo_t *af = sessobj(sess).audiofifo; audio_fifo_data_t *afd = NULL; size_t s; if (num_frames == 0) return 0; // Audio discontinuity, do nothing pthread_mutex_lock(&af->mutex); /* Buffer one second of audio */ if (af->qlen > format->sample_rate) { pthread_mutex_unlock(&af->mutex); return 0; } s = num_frames * sizeof(int16_t) * format->channels; afd = malloc(sizeof(audio_fifo_data_t) + s); memcpy(afd->samples, frames, s); afd->nsamples = num_frames; afd->rate = format->sample_rate; afd->channels = format->channels; TAILQ_INSERT_TAIL(&af->q, afd, link); af->qlen += num_frames; pthread_cond_signal(&af->cond); pthread_mutex_unlock(&af->mutex); return num_frames; } static void play_token_lost(sp_session *session) { NSObject *delegate = sessobj(session).delegate; if([delegate respondsToSelector:@selector(sessionLostPlayToken:)]) [delegate sessionLostPlayToken:sessobj(session)]; } static void log_message(sp_session *session, const char *message) { NSObject *delegate = sessobj(session).delegate; NSString *msg = [[NSString alloc] initWithUTF8String:message]; if([delegate respondsToSelector:@selector(session:logged:)]) [sessobj(session) performSelectorOnMainThread:@selector(tellDelegateThatSessionLogged:) withObject:msg waitUntilDone:NO]; } static void end_of_track(sp_session *session) { NSObject *delegate = sessobj(session).delegate; if([delegate respondsToSelector:@selector(sessionEndedPlayingTrack:)]) [delegate performSelectorOnMainThread:@selector(sessionEndedPlayingTrack:) withObject:sessobj(session) waitUntilDone:NO]; } static void streaming_error(sp_session *session, sp_error error) { NSObject *delegate = sessobj(session).delegate; NSError *err = SP_ERROR_OK == error ? nil : makeError(error); if([delegate respondsToSelector:@selector(session:streamingError:)]) [delegate session:sessobj(session) streamingError:err]; } static void userinfo_updated(sp_session *session) { NSObject *delegate = sessobj(session).delegate; if([delegate respondsToSelector:@selector(sessionUpdatedUserinfo:)]) [delegate sessionUpdatedUserinfo:sessobj(session)]; } #pragma mark @implementation SpotifySession #pragma mark - @synthesize delegate; -(id)initError:(NSError**)error; { audio_init(&audiofifo); NSArray *cachesDirs = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSArray *supportDirs = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); cachesDir = [[cachesDirs objectAtIndex:0] retain]; supportDir = [[supportDirs objectAtIndex:0] retain]; callbacks = (sp_session_callbacks){ .logged_in = logged_in, .logged_out = logged_out, .metadata_updated = metadata_updated, .connection_error = connection_error, .message_to_user = message_to_user, .notify_main_thread = notify_main_thread, .music_delivery = music_delivery, .play_token_lost = play_token_lost, .log_message = log_message, .end_of_track = end_of_track, .streaming_error = streaming_error, .userinfo_updated = userinfo_updated, }; config = (sp_session_config){ .api_version = SPOTIFY_API_VERSION, .cache_location = [cachesDir UTF8String], .settings_location = [supportDir UTF8String], .application_key = g_appkey, .application_key_size = g_appkey_size, .user_agent = [[[NSProcessInfo processInfo] processName] UTF8String], .callbacks = &callbacks, .userdata = self, }; sp_error sperror = sp_session_create(&config, &session); if (SP_ERROR_OK != sperror) { if(error) *error = makeError(sperror); [self release]; return nil; } [self processEvents]; return self; } -(void)dealloc; { // todo: shutdown audiofifo [cachesDir release]; [supportDir release]; [super dealloc]; } -(void)shutdown; { [NSObject cancelPreviousPerformRequestsWithTarget:self]; // Cancel any pending processEvents } -(void)loginUser:(NSString*)user password:(NSString*)passwd; { sp_session_login(session, [user UTF8String], [passwd UTF8String], 0, NULL); } -(void)logout; { sp_session_logout(session); } -(SpotifyPlaylist*)starred; { sp_playlist *pl = sp_session_starred_create(session); SpotifyPlaylist *playlist = [[[SpotifyPlaylist alloc] initWithPlaylist:pl onSession:session] autorelease]; sp_playlist_release(pl); return playlist; } -(void)loadTrack:(SpotifyTrack *)track; { sp_session_player_load(session, track.track); } -(void)pause; { audio_fifo_flush(&audiofifo); sp_session_player_play(session, FALSE); } -(void)play; { audio_fifo_flush(&audiofifo); sp_session_player_play(session, TRUE); } #pragma mark #pragma mark Private #pragma mark - -(void)processEvents; { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:_cmd object:nil]; int msTilNext = 0; while(msTilNext == 0) sp_session_process_events(session, &msTilNext); NSTimeInterval secsTilNext = msTilNext/1000.; [self performSelector:_cmd withObject:nil afterDelay:secsTilNext]; } -(audio_fifo_t*)audiofifo; { return &audiofifo; } -(void)tellDelegateThatSessionLogged:(NSString*)msg; { [delegate session:self logged:msg]; [msg release]; } @end ================================================ FILE: libspotify/examples/Randomify/SpotifyTrack.h ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * This file is part of the libspotify examples suite. * See RandomifyAppDelegate.h for license. */ #import #import @interface SpotifyTrack : NSObject { sp_track *track; sp_session *session; } -(id)initWithTrack:(sp_track*)track_ onSession:(sp_session*)sess; @property (readonly) NSString *name; @property (readonly) NSString *artistName; @property (readonly) BOOL loaded; @property (readonly) BOOL available; // Private @property (readonly) sp_track *track; @end ================================================ FILE: libspotify/examples/Randomify/SpotifyTrack.m ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * This file is part of the libspotify examples suite. * See RandomifyAppDelegate.h for license. */ #import "SpotifyTrack.h" @implementation SpotifyTrack @synthesize track; -(id)initWithTrack:(sp_track*)track_ onSession:(sp_session*)sess; { track = track_; session = sess; sp_track_add_ref(track); return self; } -(void)dealloc; { sp_track_release(track); [super dealloc]; } -(NSString*)name; { const char *name = sp_track_name(track); if(!name || !sp_track_is_loaded(track)) return @"[loading]"; return [NSString stringWithUTF8String:sp_track_name(track)]; } -(NSString*)artistName; { sp_artist *artist = sp_track_artist(track, 0); const char *artistName = artist ? sp_artist_name(artist) : "[loading]"; return [NSString stringWithUTF8String:artistName]; } -(BOOL)loaded; { return sp_track_is_loaded(track); } -(BOOL)available; { return sp_track_get_availability(session, track) == SP_TRACK_AVAILABILITY_AVAILABLE; } -(NSString*)description; { return [NSString stringWithFormat:@"<%@ by %@>", self.name, self.artistName ]; } @end ================================================ FILE: libspotify/examples/Randomify/main.m ================================================ // // main.m // Randomify // // Created by Joachim Bengtsson on 2010-03-30. // Copyright 2010 Spotify. All rights reserved. // #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **) argv); } ================================================ FILE: libspotify/examples/appkey.c ================================================ #error #error "You need to replace this file with a libspotify API key provided by Spotify. Please see https://developer.spotify.com/en/libspotify/application-key/" #error ================================================ FILE: libspotify/examples/common.mk ================================================ # Copyright (c) 2010 Spotify Ltd all: check-libspotify $(TARGET) # # Direct path to libspotify # ifdef LIBSPOTIFY_PATH P=$(shell cd "$(LIBSPOTIFY_PATH)" && pwd) check-libspotify: @test -f $(P)/lib/libspotify.so || (echo "Failed to find libspotify.so in $(P)/lib" >&2 ; exit 1) @test -f $(P)/include/libspotify/api.h || (echo "Failed to find libspotify/api.h in $(P)/include" >&2 ; exit 1) CFLAGS += -I$(P)/include LDFLAGS += -Wl,-rpath,$(P)/lib -L$(P)/lib LDLIBS += -lspotify ifeq ($(shell uname),Darwin) CPUARCH ?= $(shell uname -m) CFLAGS += -D__APPLE__ -arch $(CPUARCH) LDFLAGS += -arch $(CPUARCH) endif # # Use pkg-config(1) # else check-libspotify: @pkg-config --exists libspotify || (echo "Failed to find libspotify using pkg-config(1)" >&2 ; exit 1) CFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --cflags libspotify) LDFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs-only-L libspotify) LDLIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs-only-l --libs-only-other libspotify) endif ifdef DEBUG CFLAGS += -g3 -O0 endif CFLAGS += -Wall .PHONY: all check-libspotify clean vpath %.c ../ clean: rm -f *.o *~ $(TARGET) ================================================ FILE: libspotify/examples/jukebox/Makefile ================================================ ifeq ($(shell uname),Darwin) ifdef USE_AUDIOQUEUE AUDIO_DRIVER ?= osx LDFLAGS += -framework AudioToolbox else AUDIO_DRIVER ?= openal LDFLAGS += -framework OpenAL endif else CFLAGS = $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --cflags alsa) LDFLAGS = $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs-only-L alsa) LDLIBS = $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs-only-l --libs-only-other alsa) AUDIO_DRIVER ?= alsa endif TARGET = jukebox ## TARGET = playtrack OBJS = $(TARGET).o appkey.o $(AUDIO_DRIVER)-audio.o audio.o $(TARGET): $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LDLIBS) ifdef DEBUG ifeq ($(shell uname),Darwin) install_name_tool -change @loader_path/../Frameworks/libspotify.framework/libspotify @rpath/libspotify.so $@ endif endif include ../common.mk audio.o: audio.c audio.h alsa-audio.o: alsa-audio.c audio.h dummy-audio.o: dummy-audio.c audio.h osx-audio.o: osx-audio.c audio.h openal-audio.o: openal-audio.c audio.h jukebox.o: jukebox.c audio.h playtrack.o: playtrack.c audio.h ================================================ FILE: libspotify/examples/jukebox/alsa-audio.c ================================================ /* * Copyright (c) 2006-2009 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * * ALSA audio output driver. * * This file is part of the libspotify examples suite. */ #include #include #include #include #include #include #include #include "audio.h" static snd_pcm_t *alsa_open(char *dev, int rate, int channels) { snd_pcm_hw_params_t *hwp; snd_pcm_sw_params_t *swp; snd_pcm_t *h; int r; int dir; snd_pcm_uframes_t period_size_min; snd_pcm_uframes_t period_size_max; snd_pcm_uframes_t buffer_size_min; snd_pcm_uframes_t buffer_size_max; snd_pcm_uframes_t period_size; snd_pcm_uframes_t buffer_size; if ((r = snd_pcm_open(&h, dev, SND_PCM_STREAM_PLAYBACK, 0) < 0)) return NULL; hwp = alloca(snd_pcm_hw_params_sizeof()); memset(hwp, 0, snd_pcm_hw_params_sizeof()); snd_pcm_hw_params_any(h, hwp); snd_pcm_hw_params_set_access(h, hwp, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(h, hwp, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_rate(h, hwp, rate, 0); snd_pcm_hw_params_set_channels(h, hwp, channels); /* Configurue period */ dir = 0; snd_pcm_hw_params_get_period_size_min(hwp, &period_size_min, &dir); dir = 0; snd_pcm_hw_params_get_period_size_max(hwp, &period_size_max, &dir); period_size = 1024; dir = 0; r = snd_pcm_hw_params_set_period_size_near(h, hwp, &period_size, &dir); if (r < 0) { fprintf(stderr, "audio: Unable to set period size %lu (%s)\n", period_size, snd_strerror(r)); snd_pcm_close(h); return NULL; } dir = 0; r = snd_pcm_hw_params_get_period_size(hwp, &period_size, &dir); if (r < 0) { fprintf(stderr, "audio: Unable to get period size (%s)\n", snd_strerror(r)); snd_pcm_close(h); return NULL; } /* Configurue buffer size */ snd_pcm_hw_params_get_buffer_size_min(hwp, &buffer_size_min); snd_pcm_hw_params_get_buffer_size_max(hwp, &buffer_size_max); buffer_size = period_size * 4; dir = 0; r = snd_pcm_hw_params_set_buffer_size_near(h, hwp, &buffer_size); if (r < 0) { fprintf(stderr, "audio: Unable to set buffer size %lu (%s)\n", buffer_size, snd_strerror(r)); snd_pcm_close(h); return NULL; } r = snd_pcm_hw_params_get_buffer_size(hwp, &buffer_size); if (r < 0) { fprintf(stderr, "audio: Unable to get buffer size (%s)\n", snd_strerror(r)); snd_pcm_close(h); return NULL; } /* write the hw params */ r = snd_pcm_hw_params(h, hwp); if (r < 0) { fprintf(stderr, "audio: Unable to configure hardware parameters (%s)\n", snd_strerror(r)); snd_pcm_close(h); return NULL; } /* * Software parameters */ swp = alloca(snd_pcm_sw_params_sizeof()); memset(hwp, 0, snd_pcm_sw_params_sizeof()); snd_pcm_sw_params_current(h, swp); r = snd_pcm_sw_params_set_avail_min(h, swp, period_size); if (r < 0) { fprintf(stderr, "audio: Unable to configure wakeup threshold (%s)\n", snd_strerror(r)); snd_pcm_close(h); return NULL; } snd_pcm_sw_params_set_start_threshold(h, swp, 0); if (r < 0) { fprintf(stderr, "audio: Unable to configure start threshold (%s)\n", snd_strerror(r)); snd_pcm_close(h); return NULL; } r = snd_pcm_sw_params(h, swp); if (r < 0) { fprintf(stderr, "audio: Cannot set soft parameters (%s)\n", snd_strerror(r)); snd_pcm_close(h); return NULL; } r = snd_pcm_prepare(h); if (r < 0) { fprintf(stderr, "audio: Cannot prepare audio for playback (%s)\n", snd_strerror(r)); snd_pcm_close(h); return NULL; } return h; } static void* alsa_audio_start(void *aux) { audio_fifo_t *af = aux; snd_pcm_t *h = NULL; int c; int cur_channels = 0; int cur_rate = 0; audio_fifo_data_t *afd; for (;;) { afd = audio_get(af); if (!h || cur_rate != afd->rate || cur_channels != afd->channels) { if (h) snd_pcm_close(h); cur_rate = afd->rate; cur_channels = afd->channels; h = alsa_open("default", cur_rate, cur_channels); if (!h) { fprintf(stderr, "Unable to open ALSA device (%d channels, %d Hz), dying\n", cur_channels, cur_rate); exit(1); } } c = snd_pcm_wait(h, 1000); if (c >= 0) c = snd_pcm_avail_update(h); if (c == -EPIPE) snd_pcm_prepare(h); snd_pcm_writei(h, afd->samples, afd->nsamples); free(afd); } } void audio_init(audio_fifo_t *af) { pthread_t tid; TAILQ_INIT(&af->q); af->qlen = 0; pthread_mutex_init(&af->mutex, NULL); pthread_cond_init(&af->cond, NULL); pthread_create(&tid, NULL, alsa_audio_start, af); } ================================================ FILE: libspotify/examples/jukebox/audio.c ================================================ /* * Copyright (c) 2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * * Audio helper functions. * * This file is part of the libspotify examples suite. */ #include "audio.h" #include audio_fifo_data_t* audio_get(audio_fifo_t *af) { audio_fifo_data_t *afd; pthread_mutex_lock(&af->mutex); while (!(afd = TAILQ_FIRST(&af->q))) pthread_cond_wait(&af->cond, &af->mutex); TAILQ_REMOVE(&af->q, afd, link); af->qlen -= afd->nsamples; pthread_mutex_unlock(&af->mutex); return afd; } void audio_fifo_flush(audio_fifo_t *af) { audio_fifo_data_t *afd; pthread_mutex_lock(&af->mutex); while((afd = TAILQ_FIRST(&af->q))) { TAILQ_REMOVE(&af->q, afd, link); free(afd); } af->qlen = 0; pthread_mutex_unlock(&af->mutex); } ================================================ FILE: libspotify/examples/jukebox/audio.h ================================================ /* * Copyright (c) 2006-2009 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * * Audio output driver. * * This file is part of the libspotify examples suite. */ #ifndef _JUKEBOX_AUDIO_H_ #define _JUKEBOX_AUDIO_H_ #include #include #include "queue.h" /* --- Types --- */ typedef struct audio_fifo_data { TAILQ_ENTRY(audio_fifo_data) link; int channels; int rate; int nsamples; int16_t samples[0]; } audio_fifo_data_t; typedef struct audio_fifo { TAILQ_HEAD(, audio_fifo_data) q; int qlen; pthread_mutex_t mutex; pthread_cond_t cond; } audio_fifo_t; /* --- Functions --- */ extern void audio_init(audio_fifo_t *af); extern void audio_fifo_flush(audio_fifo_t *af); audio_fifo_data_t* audio_get(audio_fifo_t *af); #endif /* _JUKEBOX_AUDIO_H_ */ ================================================ FILE: libspotify/examples/jukebox/dummy-audio.c ================================================ /* * Copyright (c) 2006-2009 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * * ALSA audio output driver. * * This file is part of the libspotify examples suite. */ #include #include #include #include #include #include #include #include "audio.h" static void* alsa_audio_start(void *aux) { audio_fifo_t *af = aux; while(1) free(audio_get(af)); return NULL; } void audio_init(audio_fifo_t *af) { pthread_t tid; TAILQ_INIT(&af->q); af->qlen = 0; pthread_mutex_init(&af->mutex, NULL); pthread_cond_init(&af->cond, NULL); pthread_create(&tid, NULL, alsa_audio_start, af); } ================================================ FILE: libspotify/examples/jukebox/jukebox.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * * This example application shows parts of the playlist and player submodules. * It also shows another way of doing synchronization between callbacks and * the main thread. * * This file is part of the libspotify examples suite. */ #include #include #include #include #include #include #include #include #include #include #include "audio.h" /* --- Data --- */ /// The application key is specific to each project, and allows Spotify /// to produce statistics on how our service is used. extern const uint8_t g_appkey[]; /// The size of the application key. extern const size_t g_appkey_size; /// The output queue for audo data static audio_fifo_t g_audiofifo; /// Synchronization mutex for the main thread static pthread_mutex_t g_notify_mutex; /// Synchronization condition variable for the main thread static pthread_cond_t g_notify_cond; /// Synchronization variable telling the main thread to process events static int g_notify_do; /// Non-zero when a track has ended and the jukebox has not yet started a new one static int g_playback_done; /// The global session handle static sp_session *g_sess; /// Handle to the playlist currently being played static sp_playlist *g_jukeboxlist; /// Name of the playlist currently being played const char *g_listname; /// Remove tracks flag static int g_remove_tracks = 0; /// Handle to the curren track static sp_track *g_currenttrack; /// Index to the next track static int g_track_index; /** * Called on various events to start playback if it hasn't been started already. * * The function simply starts playing the first track of the playlist. */ static void try_jukebox_start(void) { sp_track *t; if (!g_jukeboxlist) return; if (!sp_playlist_num_tracks(g_jukeboxlist)) { fprintf(stderr, "jukebox: No tracks in playlist. Waiting\n"); return; } if (sp_playlist_num_tracks(g_jukeboxlist) < g_track_index) { fprintf(stderr, "jukebox: No more tracks in playlist. Waiting\n"); return; } t = sp_playlist_track(g_jukeboxlist, g_track_index); if (g_currenttrack && t != g_currenttrack) { /* Someone changed the current track */ audio_fifo_flush(&g_audiofifo); sp_session_player_unload(g_sess); g_currenttrack = NULL; } if (!t) return; if (sp_track_error(t) != SP_ERROR_OK) return; if (g_currenttrack == t) return; g_currenttrack = t; printf("jukebox: Now playing \"%s\"...\n", sp_track_name(t)); fflush(stdout); sp_session_player_load(g_sess, t); sp_session_player_play(g_sess, 1); } /* -------------------------- PLAYLIST CALLBACKS ------------------------- */ /** * Callback from libspotify, saying that a track has been added to a playlist. * * @param pl The playlist handle * @param tracks An array of track handles * @param num_tracks The number of tracks in the \c tracks array * @param position Where the tracks were inserted * @param userdata The opaque pointer */ static void tracks_added(sp_playlist *pl, sp_track * const *tracks, int num_tracks, int position, void *userdata) { if (pl != g_jukeboxlist) return; printf("jukebox: %d tracks were added\n", num_tracks); fflush(stdout); try_jukebox_start(); } /** * Callback from libspotify, saying that a track has been added to a playlist. * * @param pl The playlist handle * @param tracks An array of track indices * @param num_tracks The number of tracks in the \c tracks array * @param userdata The opaque pointer */ static void tracks_removed(sp_playlist *pl, const int *tracks, int num_tracks, void *userdata) { int i, k = 0; if (pl != g_jukeboxlist) return; for (i = 0; i < num_tracks; ++i) if (tracks[i] < g_track_index) ++k; g_track_index -= k; printf("jukebox: %d tracks were removed\n", num_tracks); fflush(stdout); try_jukebox_start(); } /** * Callback from libspotify, telling when tracks have been moved around in a playlist. * * @param pl The playlist handle * @param tracks An array of track indices * @param num_tracks The number of tracks in the \c tracks array * @param new_position To where the tracks were moved * @param userdata The opaque pointer */ static void tracks_moved(sp_playlist *pl, const int *tracks, int num_tracks, int new_position, void *userdata) { if (pl != g_jukeboxlist) return; printf("jukebox: %d tracks were moved around\n", num_tracks); fflush(stdout); try_jukebox_start(); } /** * Callback from libspotify. Something renamed the playlist. * * @param pl The playlist handle * @param userdata The opaque pointer */ static void playlist_renamed(sp_playlist *pl, void *userdata) { const char *name = sp_playlist_name(pl); if (!strcasecmp(name, g_listname)) { g_jukeboxlist = pl; g_track_index = 0; try_jukebox_start(); } else if (g_jukeboxlist == pl) { printf("jukebox: current playlist renamed to \"%s\".\n", name); g_jukeboxlist = NULL; g_currenttrack = NULL; sp_session_player_unload(g_sess); } } /** * The callbacks we are interested in for individual playlists. */ static sp_playlist_callbacks pl_callbacks = { .tracks_added = &tracks_added, .tracks_removed = &tracks_removed, .tracks_moved = &tracks_moved, .playlist_renamed = &playlist_renamed, }; /* -------------------- PLAYLIST CONTAINER CALLBACKS --------------------- */ /** * Callback from libspotify, telling us a playlist was added to the playlist container. * * We add our playlist callbacks to the newly added playlist. * * @param pc The playlist container handle * @param pl The playlist handle * @param position Index of the added playlist * @param userdata The opaque pointer */ static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl, int position, void *userdata) { sp_playlist_add_callbacks(pl, &pl_callbacks, NULL); if (!strcasecmp(sp_playlist_name(pl), g_listname)) { g_jukeboxlist = pl; try_jukebox_start(); } } /** * Callback from libspotify, telling us a playlist was removed from the playlist container. * * This is the place to remove our playlist callbacks. * * @param pc The playlist container handle * @param pl The playlist handle * @param position Index of the removed playlist * @param userdata The opaque pointer */ static void playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl, int position, void *userdata) { sp_playlist_remove_callbacks(pl, &pl_callbacks, NULL); } /** * Callback from libspotify, telling us the rootlist is fully synchronized * We just print an informational message * * @param pc The playlist container handle * @param userdata The opaque pointer */ static void container_loaded(sp_playlistcontainer *pc, void *userdata) { fprintf(stderr, "jukebox: Rootlist synchronized (%d playlists)\n", sp_playlistcontainer_num_playlists(pc)); } /** * The playlist container callbacks */ static sp_playlistcontainer_callbacks pc_callbacks = { .playlist_added = &playlist_added, .playlist_removed = &playlist_removed, .container_loaded = &container_loaded, }; /* --------------------------- SESSION CALLBACKS ------------------------- */ /** * This callback is called when an attempt to login has succeeded or failed. * * @sa sp_session_callbacks#logged_in */ static void logged_in(sp_session *sess, sp_error error) { sp_playlistcontainer *pc = sp_session_playlistcontainer(sess); int i; if (SP_ERROR_OK != error) { fprintf(stderr, "jukebox: Login failed: %s\n", sp_error_message(error)); exit(2); } sp_playlistcontainer_add_callbacks( pc, &pc_callbacks, NULL); printf("jukebox: Looking at %d playlists\n", sp_playlistcontainer_num_playlists(pc)); for (i = 0; i < sp_playlistcontainer_num_playlists(pc); ++i) { sp_playlist *pl = sp_playlistcontainer_playlist(pc, i); sp_playlist_add_callbacks(pl, &pl_callbacks, NULL); if (!strcasecmp(sp_playlist_name(pl), g_listname)) { g_jukeboxlist = pl; try_jukebox_start(); } } if (!g_jukeboxlist) { printf("jukebox: No such playlist. Waiting for one to pop up...\n"); fflush(stdout); } } /** * This callback is called from an internal libspotify thread to ask us to * reiterate the main loop. * * We notify the main thread using a condition variable and a protected variable. * * @sa sp_session_callbacks#notify_main_thread */ static void notify_main_thread(sp_session *sess) { pthread_mutex_lock(&g_notify_mutex); g_notify_do = 1; pthread_cond_signal(&g_notify_cond); pthread_mutex_unlock(&g_notify_mutex); } /** * This callback is used from libspotify whenever there is PCM data available. * * @sa sp_session_callbacks#music_delivery */ static int music_delivery(sp_session *sess, const sp_audioformat *format, const void *frames, int num_frames) { audio_fifo_t *af = &g_audiofifo; audio_fifo_data_t *afd; size_t s; if (num_frames == 0) return 0; // Audio discontinuity, do nothing pthread_mutex_lock(&af->mutex); /* Buffer one second of audio */ if (af->qlen > format->sample_rate) { pthread_mutex_unlock(&af->mutex); return 0; } s = num_frames * sizeof(int16_t) * format->channels; afd = malloc(sizeof(*afd) + s); memcpy(afd->samples, frames, s); afd->nsamples = num_frames; afd->rate = format->sample_rate; afd->channels = format->channels; TAILQ_INSERT_TAIL(&af->q, afd, link); af->qlen += num_frames; pthread_cond_signal(&af->cond); pthread_mutex_unlock(&af->mutex); return num_frames; } /** * This callback is used from libspotify when the current track has ended * * @sa sp_session_callbacks#end_of_track */ static void end_of_track(sp_session *sess) { pthread_mutex_lock(&g_notify_mutex); g_playback_done = 1; g_notify_do = 1; pthread_cond_signal(&g_notify_cond); pthread_mutex_unlock(&g_notify_mutex); } /** * Callback called when libspotify has new metadata available * * Not used in this example (but available to be able to reuse the session.c file * for other examples.) * * @sa sp_session_callbacks#metadata_updated */ static void metadata_updated(sp_session *sess) { try_jukebox_start(); } /** * Notification that some other connection has started playing on this account. * Playback has been stopped. * * @sa sp_session_callbacks#play_token_lost */ static void play_token_lost(sp_session *sess) { audio_fifo_flush(&g_audiofifo); if (g_currenttrack != NULL) { sp_session_player_unload(g_sess); g_currenttrack = NULL; } } /** * The session callbacks */ static sp_session_callbacks session_callbacks = { .logged_in = &logged_in, .notify_main_thread = ¬ify_main_thread, .music_delivery = &music_delivery, .metadata_updated = &metadata_updated, .play_token_lost = &play_token_lost, .log_message = NULL, .end_of_track = &end_of_track, }; /** * The session configuration. Note that application_key_size is an external, so * we set it in main() instead. */ static sp_session_config spconfig = { .api_version = SPOTIFY_API_VERSION, .cache_location = "tmp", .settings_location = "tmp", .application_key = g_appkey, .application_key_size = 0, // Set in main() .user_agent = "spotify-jukebox-example", .callbacks = &session_callbacks, NULL, }; /* ------------------------- END SESSION CALLBACKS ----------------------- */ /** * A track has ended. Remove it from the playlist. * * Called from the main loop when the music_delivery() callback has set g_playback_done. */ static void track_ended(void) { int tracks = 0; if (g_currenttrack) { g_currenttrack = NULL; sp_session_player_unload(g_sess); if (g_remove_tracks) { sp_playlist_remove_tracks(g_jukeboxlist, &tracks, 1); } else { ++g_track_index; try_jukebox_start(); } } } /** * Show usage information * * @param progname The program name */ static void usage(const char *progname) { fprintf(stderr, "usage: %s -u -p -l [-d]\n", progname); fprintf(stderr, "warning: -d will delete the tracks played from the list!\n"); } int main(int argc, char **argv) { sp_session *sp; sp_error err; int next_timeout = 0; const char *username = NULL; const char *password = NULL; int opt; while ((opt = getopt(argc, argv, "u:p:l:d")) != EOF) { switch (opt) { case 'u': username = optarg; break; case 'p': password = optarg; break; case 'l': g_listname = optarg; break; case 'd': g_remove_tracks = 1; break; default: exit(1); } } if (!username || !password || !g_listname) { usage(basename(argv[0])); exit(1); } audio_init(&g_audiofifo); /* Create session */ spconfig.application_key_size = g_appkey_size; err = sp_session_create(&spconfig, &sp); if (SP_ERROR_OK != err) { fprintf(stderr, "Unable to create session: %s\n", sp_error_message(err)); exit(1); } g_sess = sp; pthread_mutex_init(&g_notify_mutex, NULL); pthread_cond_init(&g_notify_cond, NULL); sp_session_login(sp, username, password, 0, NULL); pthread_mutex_lock(&g_notify_mutex); for (;;) { if (next_timeout == 0) { while(!g_notify_do) pthread_cond_wait(&g_notify_cond, &g_notify_mutex); } else { struct timespec ts; #if _POSIX_TIMERS > 0 clock_gettime(CLOCK_REALTIME, &ts); #else struct timeval tv; gettimeofday(&tv, NULL); TIMEVAL_TO_TIMESPEC(&tv, &ts); #endif ts.tv_sec += next_timeout / 1000; ts.tv_nsec += (next_timeout % 1000) * 1000000; pthread_cond_timedwait(&g_notify_cond, &g_notify_mutex, &ts); } g_notify_do = 0; pthread_mutex_unlock(&g_notify_mutex); if (g_playback_done) { track_ended(); g_playback_done = 0; } do { sp_session_process_events(sp, &next_timeout); } while (next_timeout == 0); pthread_mutex_lock(&g_notify_mutex); } return 0; } ================================================ FILE: libspotify/examples/jukebox/openal-audio.c ================================================ /* * Copyright (c) 2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * * OpenAL audio output driver. * * This file is part of the libspotify examples suite. */ #include #include #include #include #include #include #include #include #include "audio.h" #define NUM_BUFFERS 3 static void error_exit(const char *msg) { puts(msg); exit(1); } static int queue_buffer(ALuint source, audio_fifo_t *af, ALuint buffer) { audio_fifo_data_t *afd = audio_get(af); alBufferData(buffer, afd->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, afd->samples, afd->nsamples * afd->channels * sizeof(short), afd->rate); alSourceQueueBuffers(source, 1, &buffer); free(afd); return 1; } static void* audio_start(void *aux) { audio_fifo_t *af = aux; audio_fifo_data_t *afd; unsigned int frame = 0; ALCdevice *device = NULL; ALCcontext *context = NULL; ALuint buffers[NUM_BUFFERS]; ALuint source; ALint processed; ALenum error; ALint rate; ALint channels; device = alcOpenDevice(NULL); /* Use the default device */ if (!device) error_exit("failed to open device"); context = alcCreateContext(device, NULL); alcMakeContextCurrent(context); alListenerf(AL_GAIN, 1.0f); alDistanceModel(AL_NONE); alGenBuffers((ALsizei)NUM_BUFFERS, buffers); alGenSources(1, &source); /* First prebuffer some audio */ queue_buffer(source, af, buffers[0]); queue_buffer(source, af, buffers[1]); queue_buffer(source, af, buffers[2]); for (;;) { alSourcePlay(source); for (;;) { /* Wait for some audio to play */ do { alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed); usleep(100); } while (!processed); /* Remove old audio from the queue.. */ alSourceUnqueueBuffers(source, 1, &buffers[frame % 3]); /* and queue some more audio */ afd = audio_get(af); alGetBufferi(buffers[frame % 3], AL_FREQUENCY, &rate); alGetBufferi(buffers[frame % 3], AL_CHANNELS, &channels); if (afd->rate != rate || afd->channels != channels) { printf("rate or channel count changed, resetting\n"); free(afd); break; } alBufferData(buffers[frame % 3], afd->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, afd->samples, afd->nsamples * afd->channels * sizeof(short), afd->rate); free(afd); alSourceQueueBuffers(source, 1, &buffers[frame % 3]); if ((error = alcGetError(device)) != AL_NO_ERROR) { printf("openal al error: %d\n", error); exit(1); } frame++; } /* Format or rate changed, so we need to reset all buffers */ alSourcei(source, AL_BUFFER, 0); alSourceStop(source); /* Make sure we don't lose the audio packet that caused the change */ alBufferData(buffers[0], afd->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, afd->samples, afd->nsamples * afd->channels * sizeof(short), afd->rate); alSourceQueueBuffers(source, 1, &buffers[0]); queue_buffer(source, af, buffers[1]); queue_buffer(source, af, buffers[2]); frame = 0; } } void audio_init(audio_fifo_t *af) { pthread_t tid; TAILQ_INIT(&af->q); af->qlen = 0; pthread_mutex_init(&af->mutex, NULL); pthread_cond_init(&af->cond, NULL); pthread_create(&tid, NULL, audio_start, af); } ================================================ FILE: libspotify/examples/jukebox/osx/jukebox.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 45; objects = { /* Begin PBXBuildFile section */ CF61ED9F115B641D00443233 /* appkey.c in Sources */ = {isa = PBXBuildFile; fileRef = CF61ED99115B641D00443233 /* appkey.c */; }; CF61EDA0115B641D00443233 /* audio.c in Sources */ = {isa = PBXBuildFile; fileRef = CF61ED9A115B641D00443233 /* audio.c */; }; CF61EDA1115B641D00443233 /* jukebox.c in Sources */ = {isa = PBXBuildFile; fileRef = CF61ED9C115B641D00443233 /* jukebox.c */; }; CF61EDA2115B641D00443233 /* openal-audio.c in Sources */ = {isa = PBXBuildFile; fileRef = CF61ED9D115B641D00443233 /* openal-audio.c */; }; CF61EDA6115B644A00443233 /* libspotify.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF61EDA5115B644A00443233 /* libspotify.framework */; }; CF61EDB0115B647B00443233 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF61EDAF115B647B00443233 /* OpenAL.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 8DD76FB20486AB0100D96B5E /* jukebox */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jukebox; sourceTree = BUILT_PRODUCTS_DIR; }; CF61ED99115B641D00443233 /* appkey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = appkey.c; path = ../../appkey.c; sourceTree = SOURCE_ROOT; }; CF61ED9A115B641D00443233 /* audio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = audio.c; path = ../audio.c; sourceTree = SOURCE_ROOT; }; CF61ED9B115B641D00443233 /* audio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio.h; path = ../audio.h; sourceTree = SOURCE_ROOT; }; CF61ED9C115B641D00443233 /* jukebox.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = jukebox.c; path = ../jukebox.c; sourceTree = SOURCE_ROOT; }; CF61ED9D115B641D00443233 /* openal-audio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "openal-audio.c"; path = "../openal-audio.c"; sourceTree = SOURCE_ROOT; }; CF61ED9E115B641D00443233 /* queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = queue.h; path = ../queue.h; sourceTree = SOURCE_ROOT; }; CF61EDA5115B644A00443233 /* libspotify.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = libspotify.framework; path = /Library/Frameworks/libspotify.framework; sourceTree = ""; }; CF61EDAF115B647B00443233 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8DD76FAD0486AB0100D96B5E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CF61EDA6115B644A00443233 /* libspotify.framework in Frameworks */, CF61EDB0115B647B00443233 /* OpenAL.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 08FB7794FE84155DC02AAC07 /* jukebox */ = { isa = PBXGroup; children = ( 08FB7795FE84155DC02AAC07 /* Source */, 1AB674ADFE9D54B511CA2CBB /* Products */, CF61EDA5115B644A00443233 /* libspotify.framework */, CF61EDAF115B647B00443233 /* OpenAL.framework */, ); name = jukebox; sourceTree = ""; }; 08FB7795FE84155DC02AAC07 /* Source */ = { isa = PBXGroup; children = ( CF61ED99115B641D00443233 /* appkey.c */, CF61ED9A115B641D00443233 /* audio.c */, CF61ED9B115B641D00443233 /* audio.h */, CF61ED9C115B641D00443233 /* jukebox.c */, CF61ED9D115B641D00443233 /* openal-audio.c */, CF61ED9E115B641D00443233 /* queue.h */, ); name = Source; sourceTree = ""; }; 1AB674ADFE9D54B511CA2CBB /* Products */ = { isa = PBXGroup; children = ( 8DD76FB20486AB0100D96B5E /* jukebox */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 8DD76FA90486AB0100D96B5E /* jukebox */ = { isa = PBXNativeTarget; buildConfigurationList = 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "jukebox" */; buildPhases = ( 057527F21163423F003FFC9D /* ShellScript */, 8DD76FAB0486AB0100D96B5E /* Sources */, 8DD76FAD0486AB0100D96B5E /* Frameworks */, 0575280B11634840003FFC9D /* ShellScript */, ); buildRules = ( ); dependencies = ( ); name = jukebox; productInstallPath = "$(HOME)/bin"; productName = jukebox; productReference = 8DD76FB20486AB0100D96B5E /* jukebox */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "jukebox" */; compatibilityVersion = "Xcode 3.1"; hasScannedForEncodings = 1; mainGroup = 08FB7794FE84155DC02AAC07 /* jukebox */; projectDirPath = ""; projectRoot = ""; targets = ( 8DD76FA90486AB0100D96B5E /* jukebox */, ); }; /* End PBXProject section */ /* Begin PBXShellScriptBuildPhase section */ 057527F21163423F003FFC9D /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; shellScript = "if [ ! -e /Library/Frameworks/libspotify.framework ]; then\n echo \"You must install libspotify.framework to /Library/Frameworks.\" >&2\n exit 1\nfi\n\nexit 0"; }; 0575280B11634840003FFC9D /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "install_name_tool -change @loader_path/../Frameworks/libspotify.framework/libspotify /Library/Frameworks/libspotify.framework/libspotify $CONFIGURATION_BUILD_DIR/$EXECUTABLE_PATH"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 8DD76FAB0486AB0100D96B5E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CF61ED9F115B641D00443233 /* appkey.c in Sources */, CF61EDA0115B641D00443233 /* audio.c in Sources */, CF61EDA1115B641D00443233 /* jukebox.c in Sources */, CF61EDA2115B641D00443233 /* openal-audio.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 1DEB928608733DD80010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/../../../..\"", ); GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; INSTALL_PATH = /usr/local/bin; PRODUCT_NAME = jukebox; }; name = Debug; }; 1DEB928708733DD80010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/../../..\"", ); GCC_MODEL_TUNING = G5; INSTALL_PATH = /usr/local/bin; PRODUCT_NAME = jukebox; }; name = Release; }; 1DEB928A08733DD80010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; ONLY_ACTIVE_ARCH = YES; PREBINDING = NO; SDKROOT = macosx10.6; }; name = Debug; }; 1DEB928B08733DD80010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PREBINDING = NO; SDKROOT = macosx10.6; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "jukebox" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DEB928608733DD80010E9CD /* Debug */, 1DEB928708733DD80010E9CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "jukebox" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DEB928A08733DD80010E9CD /* Debug */, 1DEB928B08733DD80010E9CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; } ================================================ FILE: libspotify/examples/jukebox/osx-audio.c ================================================ /* * Copyright (c) 2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * * OSX AudioQueue output driver. * * This file is part of the libspotify examples suite. */ #include #include "audio.h" #define BUFFER_COUNT 7 static struct AQPlayerState { AudioStreamBasicDescription desc; AudioQueueRef queue; AudioQueueBufferRef buffers[BUFFER_COUNT]; unsigned buffer_size; } state; static void audio_callback (void *aux, AudioQueueRef aq, AudioQueueBufferRef bufout) { audio_fifo_t *af = aux; audio_fifo_data_t *afd = audio_get(af); bufout->mAudioDataByteSize = afd->nsamples * sizeof(short) * afd->channels; assert(bufout->mAudioDataByteSize <= state.buffer_size); memcpy(bufout->mAudioData, afd->samples, bufout->mAudioDataByteSize); AudioQueueEnqueueBuffer(state.queue, bufout, 0, NULL); free(afd); } static const int kSampleCountPerBuffer = 2048; void audio_init(audio_fifo_t *af) { int i; TAILQ_INIT(&af->q); af->qlen = 0; pthread_mutex_init(&af->mutex, NULL); pthread_cond_init(&af->cond, NULL); bzero(&state, sizeof(state)); state.desc.mFormatID = kAudioFormatLinearPCM; state.desc.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; state.desc.mSampleRate = 44100; state.desc.mChannelsPerFrame = 2; state.desc.mFramesPerPacket = 1; state.desc.mBytesPerFrame = sizeof(short) * state.desc.mChannelsPerFrame; state.desc.mBytesPerPacket = state.desc.mBytesPerFrame; state.desc.mBitsPerChannel = (state.desc.mBytesPerFrame*8)/state.desc.mChannelsPerFrame; state.desc.mReserved = 0; state.buffer_size = state.desc.mBytesPerFrame * kSampleCountPerBuffer; if (noErr != AudioQueueNewOutput(&state.desc, audio_callback, af, NULL, NULL, 0, &state.queue)) { printf("audioqueue error\n"); return; } // Start some empty playback so we'll get the callbacks that fill in the actual audio. for (i = 0; i < BUFFER_COUNT; ++i) { AudioQueueAllocateBuffer(state.queue, state.buffer_size, &state.buffers[i]); state.buffers[i]->mAudioDataByteSize = state.buffer_size; AudioQueueEnqueueBuffer(state.queue, state.buffers[i], 0, NULL); } if (noErr != AudioQueueStart(state.queue, NULL)) puts("AudioQueueStart failed"); } ================================================ FILE: libspotify/examples/jukebox/playtrack.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * * This example application is the most minimal way to just play a spotify URI. * * This file is part of the libspotify examples suite. */ #include #include #include #include #include #include #include #include #include #include #include "audio.h" /* --- Data --- */ /// The application key is specific to each project, and allows Spotify /// to produce statistics on how our service is used. extern const uint8_t g_appkey[]; /// The size of the application key. extern const size_t g_appkey_size; /// The output queue for audo data static audio_fifo_t g_audiofifo; /// Synchronization mutex for the main thread static pthread_mutex_t g_notify_mutex; /// Synchronization condition variable for the main thread static pthread_cond_t g_notify_cond; /// Synchronization variable telling the main thread to process events static int g_notify_do; /// Non-zero when a track has ended and the jukebox has not yet started a new one static int g_playback_done; /// The global session handle static sp_session *g_sess; /// Handle to the curren track static sp_track *g_currenttrack; /* --------------------------- SESSION CALLBACKS ------------------------- */ /** * This callback is called when an attempt to login has succeeded or failed. * * @sa sp_session_callbacks#logged_in */ static void logged_in(sp_session *sess, sp_error error) { sp_link *link; if (SP_ERROR_OK != error) { fprintf(stderr, "Login failed: %s\n", sp_error_message(error)); exit(2); } printf("Loading track\n"); link = sp_link_create_from_string("spotify:track:5W3cjX2J3tjhG8zb6u0qHn"); sp_track_add_ref(g_currenttrack = sp_link_as_track(link)); sp_link_release(link); if (sp_track_error(g_currenttrack) == SP_ERROR_OK) { printf("Now playing \"%s\"...\n", sp_track_name(g_currenttrack)); fflush(stdout); sp_session_player_load(g_sess, g_currenttrack); sp_session_player_play(g_sess, 1); } /* Track not loaded? Then we need to wait for the metadata to load before we can start playback (see below) */ } /** * Callback called when libspotify has new metadata available * * @sa sp_session_callbacks#metadata_updated */ static void metadata_updated(sp_session *sess) { puts("Metadata updated, trying to start playback"); if (sp_track_error(g_currenttrack) != SP_ERROR_OK) return; printf("Now playing \"%s\"...\n", sp_track_name(g_currenttrack)); fflush(stdout); sp_session_player_load(g_sess, g_currenttrack); sp_session_player_play(g_sess, 1); } /** * This callback is called from an internal libspotify thread to ask * us to reiterate the main loop. * * We notify the main thread using a condition variable and a protected variable. * * @sa sp_session_callbacks#notify_main_thread */ static void notify_main_thread(sp_session *sess) { pthread_mutex_lock(&g_notify_mutex); g_notify_do = 1; pthread_cond_signal(&g_notify_cond); pthread_mutex_unlock(&g_notify_mutex); } /** * This callback is used from libspotify whenever there is PCM data available. * * @sa sp_session_callbacks#music_delivery */ static int music_delivery(sp_session *sess, const sp_audioformat *format, const void *frames, int num_frames) { audio_fifo_t *af = &g_audiofifo; audio_fifo_data_t *afd; size_t s; if (num_frames == 0) return 0; // Audio discontinuity, do nothing pthread_mutex_lock(&af->mutex); /* Buffer one second of audio */ if (af->qlen > format->sample_rate) { pthread_mutex_unlock(&af->mutex); return 0; } s = num_frames * sizeof(int16_t) * format->channels; afd = malloc(sizeof(audio_fifo_data_t) + s); memcpy(afd->samples, frames, s); afd->nsamples = num_frames; afd->rate = format->sample_rate; afd->channels = format->channels; TAILQ_INSERT_TAIL(&af->q, afd, link); af->qlen += num_frames; pthread_cond_signal(&af->cond); pthread_mutex_unlock(&af->mutex); return num_frames; } /** * This callback is used from libspotify when the current track has ended * * @sa sp_session_callbacks#end_of_track */ static void end_of_track(sp_session *sess) { pthread_mutex_lock(&g_notify_mutex); g_playback_done = 1; pthread_cond_signal(&g_notify_cond); pthread_mutex_unlock(&g_notify_mutex); } /** * Notification that some other connection has started playing on this account. * Playback has been stopped. * * @sa sp_session_callbacks#play_token_lost */ static void play_token_lost(sp_session *sess) { audio_fifo_flush(&g_audiofifo); if (g_currenttrack != NULL) { sp_session_player_unload(g_sess); g_currenttrack = NULL; } } static void log_message(sp_session *session, const char *msg) { puts(msg); } /** * The session callbacks */ static sp_session_callbacks session_callbacks = { .logged_in = &logged_in, .notify_main_thread = ¬ify_main_thread, .music_delivery = &music_delivery, .metadata_updated = &metadata_updated, .play_token_lost = &play_token_lost, .log_message = &log_message, .end_of_track = &end_of_track, }; /** * The session configuration. Note that application_key_size is an * external, so we set it in main() instead. */ static sp_session_config spconfig = { .api_version = SPOTIFY_API_VERSION, .cache_location = "tmp", .settings_location = "tmp", .application_key = g_appkey, .application_key_size = 0, // Set in main() .user_agent = "spotify-playtrack-example", .callbacks = &session_callbacks, NULL, }; /* ------------------------- END SESSION CALLBACKS ----------------------- */ /** * A track has ended. Remove it from the playlist. * * Called from the main loop when the music_delivery() callback has set g_playback_done. */ static void track_ended(void) { if (g_currenttrack) { sp_track_release(g_currenttrack); g_currenttrack = NULL; sp_session_player_unload(g_sess); exit(0); } } /** * Show usage information * * @param progname The program name */ static void usage(const char *progname) { fprintf(stderr, "usage: %s -u -p \n", progname); } int main(int argc, char **argv) { sp_session *sp; sp_error err; int next_timeout = 0; const char *username = NULL; const char *password = NULL; int opt; while ((opt = getopt(argc, argv, "u:p:")) != EOF) { switch (opt) { case 'u': username = optarg; break; case 'p': password = optarg; break; default: exit(1); } } if (!username || !password) { usage(basename(argv[0])); exit(1); } audio_init(&g_audiofifo); /* Create session */ spconfig.application_key_size = g_appkey_size; err = sp_session_create(&spconfig, &sp); if (SP_ERROR_OK != err) { fprintf(stderr, "Unable to create session: %s\n", sp_error_message(err)); exit(1); } g_sess = sp; pthread_mutex_init(&g_notify_mutex, NULL); pthread_cond_init(&g_notify_cond, NULL); sp_session_login(sp, username, password, 0, NULL); pthread_mutex_lock(&g_notify_mutex); for (;;) { if (next_timeout == 0) { while(!g_notify_do && !g_playback_done) pthread_cond_wait(&g_notify_cond, &g_notify_mutex); } else { struct timespec ts; #if _POSIX_TIMERS > 0 clock_gettime(CLOCK_REALTIME, &ts); #else struct timeval tv; gettimeofday(&tv, NULL); TIMEVAL_TO_TIMESPEC(&tv, &ts); #endif ts.tv_sec += next_timeout / 1000; ts.tv_nsec += (next_timeout % 1000) * 1000000; pthread_cond_timedwait(&g_notify_cond, &g_notify_mutex, &ts); } g_notify_do = 0; pthread_mutex_unlock(&g_notify_mutex); if (g_playback_done) { track_ended(); g_playback_done = 0; } do { sp_session_process_events(sp, &next_timeout); } while (next_timeout == 0); pthread_mutex_lock(&g_notify_mutex); } return 0; } ================================================ FILE: libspotify/examples/jukebox/queue.h ================================================ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 */ #ifndef _SYS_QUEUE_H_ #define _SYS_QUEUE_H_ /* * This file defines five types of data structures: singly-linked lists, * lists, simple queues, tail queues, and circular queues. * * A singly-linked list is headed by a single forward pointer. The * elements are singly linked for minimum space and pointer manipulation * overhead at the expense of O(n) removal for arbitrary elements. New * elements can be added to the list after an existing element or at the * head of the list. Elements being removed from the head of the list * should use the explicit macro for this purpose for optimum * efficiency. A singly-linked list may only be traversed in the forward * direction. Singly-linked lists are ideal for applications with large * datasets and few or no removals or for implementing a LIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A simple queue is headed by a pair of pointers, one the head of the * list and the other to the tail of the list. The elements are singly * linked to save space, so elements can only be removed from the * head of the list. New elements can be added to the list after * an existing element, at the head of the list, or at the end of the * list. A simple queue may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * A circle queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the list. * A circle queue may be traversed in either direction, but has a more * complex end of list detection. * * For details on the use of these macros, see the queue(3) manual page. */ /* * List definitions. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List functions. */ #define LIST_INIT(head) do { \ (head)->lh_first = NULL; \ } while (/*CONSTCOND*/0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ (listelm)->field.le_next->field.le_prev = \ &(elm)->field.le_next; \ (listelm)->field.le_next = (elm); \ (elm)->field.le_prev = &(listelm)->field.le_next; \ } while (/*CONSTCOND*/0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.le_prev = (listelm)->field.le_prev; \ (elm)->field.le_next = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &(elm)->field.le_next; \ } while (/*CONSTCOND*/0) #define LIST_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.le_next = (head)->lh_first) != NULL) \ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ (head)->lh_first = (elm); \ (elm)->field.le_prev = &(head)->lh_first; \ } while (/*CONSTCOND*/0) #define LIST_REMOVE(elm, field) do { \ if ((elm)->field.le_next != NULL) \ (elm)->field.le_next->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = (elm)->field.le_next; \ } while (/*CONSTCOND*/0) #define LIST_FOREACH(var, head, field) \ for ((var) = ((head)->lh_first); \ (var); \ (var) = ((var)->field.le_next)) /* * List access methods. */ #define LIST_EMPTY(head) ((head)->lh_first == NULL) #define LIST_FIRST(head) ((head)->lh_first) #define LIST_NEXT(elm, field) ((elm)->field.le_next) /* * Singly-linked List definitions. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List functions. */ #define SLIST_INIT(head) do { \ (head)->slh_first = NULL; \ } while (/*CONSTCOND*/0) #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ (elm)->field.sle_next = (slistelm)->field.sle_next; \ (slistelm)->field.sle_next = (elm); \ } while (/*CONSTCOND*/0) #define SLIST_INSERT_HEAD(head, elm, field) do { \ (elm)->field.sle_next = (head)->slh_first; \ (head)->slh_first = (elm); \ } while (/*CONSTCOND*/0) #define SLIST_REMOVE_HEAD(head, field) do { \ (head)->slh_first = (head)->slh_first->field.sle_next; \ } while (/*CONSTCOND*/0) #define SLIST_REMOVE(head, elm, type, field) do { \ if ((head)->slh_first == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = (head)->slh_first; \ while(curelm->field.sle_next != (elm)) \ curelm = curelm->field.sle_next; \ curelm->field.sle_next = \ curelm->field.sle_next->field.sle_next; \ } \ } while (/*CONSTCOND*/0) #define SLIST_FOREACH(var, head, field) \ for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next) /* * Singly-linked List access methods. */ #define SLIST_EMPTY(head) ((head)->slh_first == NULL) #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) /* * Singly-linked Tail queue declarations. */ #define STAILQ_HEAD(name, type) \ struct name { \ struct type *stqh_first; /* first element */ \ struct type **stqh_last; /* addr of last next element */ \ } #define STAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).stqh_first } #define STAILQ_ENTRY(type) \ struct { \ struct type *stqe_next; /* next element */ \ } /* * Singly-linked Tail queue functions. */ #define STAILQ_INIT(head) do { \ (head)->stqh_first = NULL; \ (head)->stqh_last = &(head)->stqh_first; \ } while (/*CONSTCOND*/0) #define STAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ (head)->stqh_last = &(elm)->field.stqe_next; \ (head)->stqh_first = (elm); \ } while (/*CONSTCOND*/0) #define STAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.stqe_next = NULL; \ *(head)->stqh_last = (elm); \ (head)->stqh_last = &(elm)->field.stqe_next; \ } while (/*CONSTCOND*/0) #define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ (head)->stqh_last = &(elm)->field.stqe_next; \ (listelm)->field.stqe_next = (elm); \ } while (/*CONSTCOND*/0) #define STAILQ_REMOVE_HEAD(head, field) do { \ if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ (head)->stqh_last = &(head)->stqh_first; \ } while (/*CONSTCOND*/0) #define STAILQ_REMOVE(head, elm, type, field) do { \ if ((head)->stqh_first == (elm)) { \ STAILQ_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = (head)->stqh_first; \ while (curelm->field.stqe_next != (elm)) \ curelm = curelm->field.stqe_next; \ if ((curelm->field.stqe_next = \ curelm->field.stqe_next->field.stqe_next) == NULL) \ (head)->stqh_last = &(curelm)->field.stqe_next; \ } \ } while (/*CONSTCOND*/0) #define STAILQ_FOREACH(var, head, field) \ for ((var) = ((head)->stqh_first); \ (var); \ (var) = ((var)->field.stqe_next)) /* * Singly-linked Tail queue access methods. */ #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) #define STAILQ_FIRST(head) ((head)->stqh_first) #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) /* * Simple queue definitions. */ #define SIMPLEQ_HEAD(name, type) \ struct name { \ struct type *sqh_first; /* first element */ \ struct type **sqh_last; /* addr of last next element */ \ } #define SIMPLEQ_HEAD_INITIALIZER(head) \ { NULL, &(head).sqh_first } #define SIMPLEQ_ENTRY(type) \ struct { \ struct type *sqe_next; /* next element */ \ } /* * Simple queue functions. */ #define SIMPLEQ_INIT(head) do { \ (head)->sqh_first = NULL; \ (head)->sqh_last = &(head)->sqh_first; \ } while (/*CONSTCOND*/0) #define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ (head)->sqh_last = &(elm)->field.sqe_next; \ (head)->sqh_first = (elm); \ } while (/*CONSTCOND*/0) #define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.sqe_next = NULL; \ *(head)->sqh_last = (elm); \ (head)->sqh_last = &(elm)->field.sqe_next; \ } while (/*CONSTCOND*/0) #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ (head)->sqh_last = &(elm)->field.sqe_next; \ (listelm)->field.sqe_next = (elm); \ } while (/*CONSTCOND*/0) #define SIMPLEQ_REMOVE_HEAD(head, field) do { \ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ (head)->sqh_last = &(head)->sqh_first; \ } while (/*CONSTCOND*/0) #define SIMPLEQ_REMOVE(head, elm, type, field) do { \ if ((head)->sqh_first == (elm)) { \ SIMPLEQ_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = (head)->sqh_first; \ while (curelm->field.sqe_next != (elm)) \ curelm = curelm->field.sqe_next; \ if ((curelm->field.sqe_next = \ curelm->field.sqe_next->field.sqe_next) == NULL) \ (head)->sqh_last = &(curelm)->field.sqe_next; \ } \ } while (/*CONSTCOND*/0) #define SIMPLEQ_FOREACH(var, head, field) \ for ((var) = ((head)->sqh_first); \ (var); \ (var) = ((var)->field.sqe_next)) /* * Simple queue access methods. */ #define SIMPLEQ_EMPTY(head) ((head)->sqh_first == NULL) #define SIMPLEQ_FIRST(head) ((head)->sqh_first) #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) /* * Tail queue definitions. */ #define _TAILQ_HEAD(name, type, qual) \ struct name { \ qual type *tqh_first; /* first element */ \ qual type *qual *tqh_last; /* addr of last next element */ \ } #define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define _TAILQ_ENTRY(type, qual) \ struct { \ qual type *tqe_next; /* next element */ \ qual type *qual *tqe_prev; /* address of previous next element */\ } #define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) /* * Tail queue functions. */ #define TAILQ_INIT(head) do { \ (head)->tqh_first = NULL; \ (head)->tqh_last = &(head)->tqh_first; \ } while (/*CONSTCOND*/0) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ (head)->tqh_first->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_first = (elm); \ (elm)->field.tqe_prev = &(head)->tqh_first; \ } while (/*CONSTCOND*/0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.tqe_next = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &(elm)->field.tqe_next; \ } while (/*CONSTCOND*/0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ (elm)->field.tqe_next->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (listelm)->field.tqe_next = (elm); \ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ } while (/*CONSTCOND*/0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ (elm)->field.tqe_next = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ } while (/*CONSTCOND*/0) #define TAILQ_REMOVE(head, elm, field) do { \ if (((elm)->field.tqe_next) != NULL) \ (elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_prev; \ else \ (head)->tqh_last = (elm)->field.tqe_prev; \ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ } while (/*CONSTCOND*/0) #define TAILQ_FOREACH(var, head, field) \ for ((var) = ((head)->tqh_first); \ (var); \ (var) = ((var)->field.tqe_next)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \ (var); \ (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) /* * Tail queue access methods. */ #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) /* * Circular queue definitions. */ #define CIRCLEQ_HEAD(name, type) \ struct name { \ struct type *cqh_first; /* first element */ \ struct type *cqh_last; /* last element */ \ } #define CIRCLEQ_HEAD_INITIALIZER(head) \ { (void *)&head, (void *)&head } #define CIRCLEQ_ENTRY(type) \ struct { \ struct type *cqe_next; /* next element */ \ struct type *cqe_prev; /* previous element */ \ } /* * Circular queue functions. */ #define CIRCLEQ_INIT(head) do { \ (head)->cqh_first = (void *)(head); \ (head)->cqh_last = (void *)(head); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm)->field.cqe_next; \ (elm)->field.cqe_prev = (listelm); \ if ((listelm)->field.cqe_next == (void *)(head)) \ (head)->cqh_last = (elm); \ else \ (listelm)->field.cqe_next->field.cqe_prev = (elm); \ (listelm)->field.cqe_next = (elm); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm); \ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ if ((listelm)->field.cqe_prev == (void *)(head)) \ (head)->cqh_first = (elm); \ else \ (listelm)->field.cqe_prev->field.cqe_next = (elm); \ (listelm)->field.cqe_prev = (elm); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ (elm)->field.cqe_next = (head)->cqh_first; \ (elm)->field.cqe_prev = (void *)(head); \ if ((head)->cqh_last == (void *)(head)) \ (head)->cqh_last = (elm); \ else \ (head)->cqh_first->field.cqe_prev = (elm); \ (head)->cqh_first = (elm); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.cqe_next = (void *)(head); \ (elm)->field.cqe_prev = (head)->cqh_last; \ if ((head)->cqh_first == (void *)(head)) \ (head)->cqh_first = (elm); \ else \ (head)->cqh_last->field.cqe_next = (elm); \ (head)->cqh_last = (elm); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_REMOVE(head, elm, field) do { \ if ((elm)->field.cqe_next == (void *)(head)) \ (head)->cqh_last = (elm)->field.cqe_prev; \ else \ (elm)->field.cqe_next->field.cqe_prev = \ (elm)->field.cqe_prev; \ if ((elm)->field.cqe_prev == (void *)(head)) \ (head)->cqh_first = (elm)->field.cqe_next; \ else \ (elm)->field.cqe_prev->field.cqe_next = \ (elm)->field.cqe_next; \ } while (/*CONSTCOND*/0) #define CIRCLEQ_FOREACH(var, head, field) \ for ((var) = ((head)->cqh_first); \ (var) != (const void *)(head); \ (var) = ((var)->field.cqe_next)) #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ for ((var) = ((head)->cqh_last); \ (var) != (const void *)(head); \ (var) = ((var)->field.cqe_prev)) /* * Circular queue access methods. */ #define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) #define CIRCLEQ_FIRST(head) ((head)->cqh_first) #define CIRCLEQ_LAST(head) ((head)->cqh_last) #define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) #define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) #define CIRCLEQ_LOOP_NEXT(head, elm, field) \ (((elm)->field.cqe_next == (void *)(head)) \ ? ((head)->cqh_first) \ : (elm->field.cqe_next)) #define CIRCLEQ_LOOP_PREV(head, elm, field) \ (((elm)->field.cqe_prev == (void *)(head)) \ ? ((head)->cqh_last) \ : (elm->field.cqe_prev)) #endif /* sys/queue.h */ ================================================ FILE: libspotify/examples/localfiles/Makefile ================================================ TARGET=posix_stu CFLAGs += -Werror include ../common.mk $(TARGET): main.o appkey.o $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LDLIBS) ifdef DEBUG ifeq ($(shell uname),Darwin) install_name_tool -change @loader_path/../Frameworks/libspotify.framework/libspotify @rpath/libspotify.so $@ endif endif ================================================ FILE: libspotify/examples/localfiles/main.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #define USER_AGENT "testlocal" /* A simple stub that logs in and out */ #include "main.h" #include #include #include #include #include sp_session *g_session; static int notify_events; static pthread_mutex_t notify_mutex; static pthread_cond_t notify_cond; static void connection_error(sp_session *session, sp_error error) { } static void logged_in(sp_session *session, sp_error error) { //add your testcode here if (error != SP_ERROR_OK) { fprintf(stderr, "failed to login: %s\n", sp_error_message(error)); exit(1); } sp_track *track = sp_localtrack_create("Turboweekend","Something Or Nothing - Acid Washed Remix","",-1); sp_playlistcontainer *pc = sp_session_playlistcontainer(session); sp_playlist *pl = sp_playlistcontainer_add_new_playlist(pc,"localtest"); sp_playlist_add_tracks(pl,&track,1,0,session); //sp_session_logout(session); } static void logged_out(sp_session *session) { exit(0); } /** * This callback is called for log messages. * * @sa sp_session_callbacks#log_message */ static void log_message(sp_session *session, const char *data) { fprintf(stderr,"%s",data); } void notify_main_thread(sp_session *session) { pthread_mutex_lock(¬ify_mutex); notify_events = 1; pthread_cond_signal(¬ify_cond); pthread_mutex_unlock(¬ify_mutex); } static sp_session_callbacks callbacks = { &logged_in, &logged_out, NULL, &connection_error, NULL, ¬ify_main_thread, NULL, NULL, &log_message }; int spotify_init(const char *username,const char *password) { sp_session_config config; sp_error error; sp_session *session; /// The application key is specific to each project, and allows Spotify /// to produce statistics on how our service is used. extern const char g_appkey[]; /// The size of the application key. extern const size_t g_appkey_size; // Always do this. It allows libspotify to check for // header/library inconsistencies. config.api_version = SPOTIFY_API_VERSION; // The path of the directory to store the cache. This must be specified. // Please read the documentation on preferred values. config.cache_location = "tmp"; // The path of the directory to store the settings. // This must be specified. // Please read the documentation on preferred values. config.settings_location = "tmp"; // The key of the application. They are generated by Spotify, // and are specific to each application using libspotify. config.application_key = g_appkey; config.application_key_size = g_appkey_size; // This identifies the application using some // free-text string [1, 255] characters. config.user_agent = USER_AGENT; // Register the callbacks. config.callbacks = &callbacks; error = sp_session_create(&config, &session); if (SP_ERROR_OK != error) { fprintf(stderr, "failed to create session: %s\n", sp_error_message(error)); return 2; } // Login using the credentials given on the command line. sp_session_login(session, username, password, 0, NULL); if (SP_ERROR_OK != error) { fprintf(stderr, "failed to login: %s\n", sp_error_message(error)); return 3; } g_session = session; return 0; } int main(int argc, char **argv) { int next_timeout = 0; if(argc < 3) { fprintf(stderr,"Usage: %s \n",argv[0]); return -1; } pthread_mutex_init(¬ify_mutex, NULL); pthread_cond_init(¬ify_cond, NULL); if(spotify_init(argv[1],argv[2]) != 0) { fprintf(stderr,"Spotify failed to initialize\n"); exit(-1); } pthread_mutex_lock(¬ify_mutex); for (;;) { if (next_timeout == 0) { while(!notify_events) pthread_cond_wait(¬ify_cond, ¬ify_mutex); } else { struct timespec ts; #if _POSIX_TIMERS > 0 clock_gettime(CLOCK_REALTIME, &ts); #else struct timeval tv; gettimeofday(&tv, NULL); TIMEVAL_TO_TIMESPEC(&tv, &ts); #endif ts.tv_sec += next_timeout / 1000; ts.tv_nsec += (next_timeout % 1000) * 1000000; while(!notify_events) { if(pthread_cond_timedwait(¬ify_cond, ¬ify_mutex, &ts)) break; } } // Process libspotify events notify_events = 0; pthread_mutex_unlock(¬ify_mutex); do { sp_session_process_events(g_session, &next_timeout); } while (next_timeout == 0); pthread_mutex_lock(¬ify_mutex); } return 0; } ================================================ FILE: libspotify/examples/localfiles/main.h ================================================ #ifndef __MAIN_H__ #define __MAIN_H__ #include #endif ================================================ FILE: libspotify/examples/spshell/Makefile ================================================ TARGET=spshell LDLIBS += -lreadline ifdef SP_LIBSPOTIFY_WITH_SCROBBLING CFLAGS += -DSP_LIBSPOTIFY_WITH_SCROBBLING=1 OBJS += scrobbling.o endif include ../common.mk OBJS += spshell.o spshell_posix.o appkey.o cmd.o browse.o search.o toplist.o inbox.o star.o playlist.o test.o $(TARGET): $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ ifdef DEBUG ifeq ($(shell uname),Darwin) install_name_tool -change @loader_path/../Frameworks/libspotify.framework/libspotify @rpath/libspotify.so $@ endif endif ================================================ FILE: libspotify/examples/spshell/browse.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "spshell.h" #include "cmd.h" static sp_track *track_browse; static sp_playlist *playlist_browse; static sp_playlist_callbacks pl_callbacks; /** * Print the given track title together with some trivial metadata * * @param track The track object */ void print_track(sp_track *track) { int duration = sp_track_duration(track); char url[256]; sp_link *l; int i; #if WIN32 printf(" %s ", sp_track_is_starred(g_session,track) ? "*" : " "); #else printf(" %s ", sp_track_is_starred(g_session,track) ? "★" : "☆"); #endif printf("Track %s [%d:%02d] has %d artist(s), %d%% popularity", sp_track_name(track), duration / 60000, (duration / 1000) % 60, sp_track_num_artists(track), sp_track_popularity(track)); if(sp_track_disc(track)) printf(", %d on disc %d", sp_track_index(track), sp_track_disc(track)); printf("\n"); for (i = 0; i < sp_track_num_artists(track); i++) { sp_artist *art = sp_track_artist(track, i); printf("\tArtist %d: %s\n", i + 1, sp_artist_name(art)); } l = sp_link_create_from_track(track, 0); sp_link_as_string(l, url, sizeof(url)); printf("\t\t%s\n", url); sp_link_release(l); } /** * Print the given album browse result and as much information as possible * * @param browse The browse result */ static void print_albumbrowse(sp_albumbrowse *browse) { int i; printf("Album browse of \"%s\" (%d)\n", sp_album_name(sp_albumbrowse_album(browse)), sp_album_year(sp_albumbrowse_album(browse))); for (i = 0; i < sp_albumbrowse_num_copyrights(browse); ++i) printf(" Copyright: %s\n", sp_albumbrowse_copyright(browse, i)); printf(" Tracks: %d\n", sp_albumbrowse_num_tracks(browse)); printf(" Review: %.60s...\n", sp_albumbrowse_review(browse)); puts(""); for (i = 0; i < sp_albumbrowse_num_tracks(browse); ++i) print_track(sp_albumbrowse_track(browse, i)); puts(""); } /** * Print the given artist browse result and as much information as possible * * @param browse The browse result */ static void print_artistbrowse(sp_artistbrowse *browse) { int i; printf("Artist browse of \"%s\"\n", sp_artist_name(sp_artistbrowse_artist(browse))); for (i = 0; i < sp_artistbrowse_num_similar_artists(browse); ++i) printf(" Similar artist: %s\n", sp_artist_name(sp_artistbrowse_similar_artist(browse, i))); printf(" Portraits: %d\n", sp_artistbrowse_num_portraits(browse)); printf(" Tracks : %d\n", sp_artistbrowse_num_tracks(browse)); printf(" Biography: %.60s...\n", sp_artistbrowse_biography(browse)); puts(""); for (i = 0; i < sp_artistbrowse_num_tracks(browse); ++i) print_track(sp_artistbrowse_track(browse, i)); puts(""); } /** * Callback for libspotify * * @param browse The browse result object that is now done * @param userdata The opaque pointer given to sp_albumbrowse_create() */ static void SP_CALLCONV browse_album_callback(sp_albumbrowse *browse, void *userdata) { if (sp_albumbrowse_error(browse) == SP_ERROR_OK) print_albumbrowse(browse); else fprintf(stderr, "Failed to browse album: %s\n", sp_error_message(sp_albumbrowse_error(browse))); sp_albumbrowse_release(browse); cmd_done(); } /** * Callback for libspotify * * @param browse The browse result object that is now done * @param userdata The opaque pointer given to sp_artistbrowse_create() */ static void SP_CALLCONV browse_artist_callback(sp_artistbrowse *browse, void *userdata) { if (sp_artistbrowse_error(browse) == SP_ERROR_OK) print_artistbrowse(browse); else fprintf(stderr, "Failed to browse artist: %s\n", sp_error_message(sp_artistbrowse_error(browse))); sp_artistbrowse_release(browse); cmd_done(); } /** * */ static void track_browse_try(void) { switch (sp_track_error(track_browse)) { case SP_ERROR_OK: print_track(track_browse); break; case SP_ERROR_IS_LOADING: return; // Still pending default: fprintf(stderr, "Unable to resolve track: %s\n", sp_error_message(sp_track_error(track_browse))); break; } metadata_updated_fn = NULL; cmd_done(); sp_track_release(track_browse); } /** * */ static void playlist_browse_try(void) { int i, tracks; metadata_updated_fn = playlist_browse_try; if(!sp_playlist_is_loaded(playlist_browse)) { printf("\tPlaylist not loaded\n"); return; } tracks = sp_playlist_num_tracks(playlist_browse); for(i = 0; i < tracks; i++) { sp_track *t = sp_playlist_track(playlist_browse, i); if (!sp_track_is_loaded(t)) return; } printf("\tPlaylist and metadata loaded\n"); for(i = 0; i < tracks; i++) { sp_track *t = sp_playlist_track(playlist_browse, i); printf(" %5d: ", i + 1); print_track(t); } sp_playlist_remove_callbacks(playlist_browse, &pl_callbacks, NULL); sp_playlist_release(playlist_browse); playlist_browse = NULL; metadata_updated_fn = NULL; cmd_done(); } /** * */ static void SP_CALLCONV pl_tracks_added(sp_playlist *pl, sp_track * const * tracks, int num_tracks, int position, void *userdata) { printf("\t%d tracks added\n", num_tracks); } /** * */ static void SP_CALLCONV pl_tracks_removed(sp_playlist *pl, const int *tracks, int num_tracks, void *userdata) { printf("\t%d tracks removed\n", num_tracks); } /** * */ static void SP_CALLCONV pl_tracks_moved(sp_playlist *pl, const int *tracks, int num_tracks, int new_position, void *userdata) { printf("\t%d tracks moved\n", num_tracks); } /** * */ static void SP_CALLCONV pl_renamed(sp_playlist *pl, void *userdata) { printf("\tList name: %s\n", sp_playlist_name(pl)); } /** * */ static void SP_CALLCONV pl_state_change(sp_playlist *pl, void *userdata) { playlist_browse_try(); } static sp_playlist_callbacks pl_callbacks = { pl_tracks_added, pl_tracks_removed, pl_tracks_moved, pl_renamed, pl_state_change, }; void browse_playlist(sp_playlist *pl) { playlist_browse = pl; sp_playlist_add_callbacks(playlist_browse, &pl_callbacks, NULL); playlist_browse_try(); } /** * */ static void browse_usage(void) { fprintf(stderr, "Usage: browse \n"); } /** * */ int cmd_browse(int argc, char **argv) { sp_link *link; if (argc != 2) { browse_usage(); return -1; } link = sp_link_create_from_string(argv[1]); if (!link) { fprintf(stderr, "Not a spotify link\n"); return -1; } switch(sp_link_type(link)) { default: fprintf(stderr, "Can not handle link"); sp_link_release(link); return -1; case SP_LINKTYPE_ALBUM: sp_albumbrowse_create(g_session, sp_link_as_album(link), browse_album_callback, NULL); break; case SP_LINKTYPE_ARTIST: sp_artistbrowse_create(g_session, sp_link_as_artist(link), SP_ARTISTBROWSE_FULL, browse_artist_callback, NULL); break; case SP_LINKTYPE_LOCALTRACK: case SP_LINKTYPE_TRACK: track_browse = sp_link_as_track(link); metadata_updated_fn = track_browse_try; sp_track_add_ref(track_browse); track_browse_try(); break; case SP_LINKTYPE_PLAYLIST: browse_playlist(sp_playlist_create(g_session, link)); break; } sp_link_release(link); return 0; } ================================================ FILE: libspotify/examples/spshell/cmd.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include "spshell.h" #include "cmd.h" static int cmd_help(int argc, char **argv); /** * */ struct { const char *name; int (*fn)(int argc, char **argv); const char *help; } commands[] = { { "log", cmd_log, "Enable/Disable logging to console (default off)" }, { "logout", cmd_logout, "Logout and exit app" }, { "exit", cmd_logout, "Logout and exit app" }, { "quit", cmd_logout, "Logout and exit app" }, { "browse", cmd_browse, "Browse a Spotify URI" }, { "search", cmd_search, "Search" }, { "whatsnew", cmd_whatsnew, "List new albums" }, { "toplist", cmd_toplist, "Browse toplists" }, { "post", cmd_post, "Post track to a user's inbox" }, { "inbox", cmd_inbox, "View inbox" }, { "help", cmd_help, "This help" }, { "star", cmd_star, "Star a track" }, { "unstar", cmd_unstar, "Unstar a track" }, { "starred", cmd_starred, "List all starred tracks" }, #if SP_LIBSPOTIFY_WITH_SCROBBLING { "facebook_scrobbling", cmd_facebook_scrobbling, "Enable/Disable facebook scrobbling" }, { "is_facebook_scrobbling", cmd_is_facebook_scrobbling, "Returns facebook scrobbling status" }, { "spotify_social", cmd_spotify_social, "Enable/Disable posting to Spotify Social" }, { "is_spotify_social", cmd_is_spotify_social, "Returns Spotify Social status" }, { "lastfm_scrobbling_credentials", cmd_set_lastfm_scrobbling_credentials, "Sets the lastfm scrobbling credentials" }, { "lastfm_scrobbling", cmd_lastfm_scrobbling, "Enable/Disable lastfm scrobbling" }, { "is_lastfm_scrobbling", cmd_is_lastfm_scrobbling, "Returns lastfm scrobbling status"}, { "private_session", cmd_private_session, "Enable/Disable private session" }, { "is_private_session", cmd_is_private_session, "Returns private session mode"}, #endif { "playlists", cmd_playlists, "List playlists" }, { "playlist", cmd_playlist, "List playlist contents" }, { "set_autolink", cmd_set_autolink, "Set autolinking state" }, { "add_folder", cmd_add_folder, "Add playlist folder"}, { "update_subscriptions", cmd_update_subscriptions, "Update playlist subscription info"}, { "add", cmd_playlist_add_track, "Add track to playlist"}, { "offline", cmd_playlist_offline, "Set offline mode for a playlist"}, #if WITH_TEST_COMMAND { "test", cmd_test, "Run tests"}, #endif }; /** * */ static int tokenize(char *buf, char **vec, int vsize) { int n = 0; while(1) { while(*buf > 0 && *buf < 33) buf++; if(!*buf) break; vec[n++] = buf; if(n == vsize) break; while(*buf > 32) buf++; if(*buf == 0) break; *buf = 0; buf++; } return n; } /** * */ void cmd_exec_unparsed(char *l) { char *vec[32]; int c = tokenize(l, vec, 32); cmd_dispatch(c, vec); } /** * */ void cmd_dispatch(int argc, char **argv) { int i; if(argc < 1) { cmd_done(); return; } for(i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { if(!strcmp(commands[i].name, argv[0])) { if(commands[i].fn(argc, argv)) cmd_done(); return; } } printf("No such command\n"); cmd_done(); } /** * */ static int cmd_help(int argc, char **argv) { int i; for(i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) printf(" %-20s %s\n", commands[i].name, commands[i].help); return -1; } ================================================ FILE: libspotify/examples/spshell/cmd.h ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #ifndef CMD_H__ #define CMD_H__ extern void cmd_exec_unparsed(char *l); extern void cmd_dispatch(int argc, char **argv); extern void cmd_done(void); extern int cmd_log(int argc, char **argv); extern int cmd_logout(int argc, char **argv); extern int cmd_browse(int argc, char **argv); extern int cmd_search(int argc, char **argv); extern int cmd_whatsnew(int argc, char **argv); extern int cmd_toplist(int argc, char **argv); extern int cmd_post(int argc, char **argv); extern int cmd_star(int argc, char **argv); extern int cmd_unstar(int argc, char **argv); extern int cmd_starred(int argc, char **argv); extern int cmd_inbox(int argc, char **argv); extern int cmd_friends(int argc, char **argv); extern int cmd_facebook_scrobbling(int argc, char **argv); extern int cmd_is_facebook_scrobbling(int argc, char **argv); extern int cmd_spotify_social(int argc, char **argv); extern int cmd_is_spotify_social(int argc, char **argv); extern int cmd_set_lastfm_scrobbling_credentials(int argc, char** argv); extern int cmd_lastfm_scrobbling(int argc, char** argv); extern int cmd_is_lastfm_scrobbling(int argc, char** argv); extern int cmd_private_session(int argc, char** argv); extern int cmd_is_private_session(int argc, char** argv); extern int cmd_playlists(int argc, char **argv); extern int cmd_playlist(int argc, char **argv); extern int cmd_set_autolink(int argc, char **argv); extern int cmd_published_playlists(int argc, char **argv); extern int cmd_get_published_playlist (int argc, char **argv); extern int cmd_social_enable(int argc, char **argv); extern int cmd_playlists_enable(int argc, char **argv); extern int cmd_add_folder(int argc, char **argv); extern int cmd_update_subscriptions(int argc, char **argv); extern int cmd_playlist_add_track(int argc, char **argv); extern int cmd_playlist_offline(int argc, char **argv); #if WITH_TEST_COMMAND extern int cmd_test(int argc, char **argv); #endif /* Shared functions */ void browse_playlist(sp_playlist *pl); void print_track(sp_track *track); #endif // CMD_H__ ================================================ FILE: libspotify/examples/spshell/inbox.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include "spshell.h" #include "cmd.h" /** * */ static void post_usage(void) { fprintf(stderr, "Usage: post [ ...]\n"); } /** * Callback for libspotify * * @param result The inbox result object that is now done * @param userdata The opaque pointer given to sp_inbox_post_tracks() */ static void SP_CALLCONV inbox_post_completed(sp_inbox *result, void *userdata) { fprintf(stderr, "Inbox post result: %s\n", sp_error_message(sp_inbox_error(result))); cmd_done(); } /** * */ int cmd_post(int argc, char **argv) { int num_tracks, i; sp_track **tracks; sp_link *link; sp_inbox *req; if (argc < 3) { post_usage(); return -1; } if (argc == 3) { // No arguments, rickroll recipient tracks = malloc(sizeof(sp_track *)); link = sp_link_create_from_string("spotify:track:6JEK0CvvjDjjMUBFoXShNZ"); sp_track_add_ref(tracks[0] = sp_link_as_track(link)); sp_link_release(link); num_tracks = 1; } else { tracks = malloc(sizeof(sp_track *) * (argc - 3)); num_tracks = 0; for(i = 3; i < argc; i++) { link = sp_link_create_from_string(argv[i]); if(link == NULL || !(sp_link_type(link) == SP_LINKTYPE_TRACK || sp_link_type(link) == SP_LINKTYPE_LOCALTRACK)) continue; sp_track_add_ref(tracks[num_tracks++] = sp_link_as_track(link)); sp_link_release(link); } } if(num_tracks == 0) { fprintf(stderr, "No valid tracks?\n"); return -1; } req = sp_inbox_post_tracks(g_session, argv[1], tracks, num_tracks, argv[2], inbox_post_completed, NULL); for(i = 0; i < num_tracks; i++) sp_track_release(tracks[i]); free(tracks); if(req == NULL) { fprintf(stderr, "inbox post failed\n"); return -1; } return 0; } /** * */ int cmd_inbox(int argc, char **argv) { sp_playlist *inbox; inbox = sp_session_inbox_create(g_session); if (!inbox) { printf("Inbox not loaded\n"); return -1; } browse_playlist(inbox); return 0; } ================================================ FILE: libspotify/examples/spshell/osx/spshell.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 45; objects = { /* Begin PBXBuildFile section */ 0575285911634BB7003FFC9D /* star.c in Sources */ = {isa = PBXBuildFile; fileRef = 0575285811634BB7003FFC9D /* star.c */; }; 0575285D11634BC4003FFC9D /* social.c in Sources */ = {isa = PBXBuildFile; fileRef = 0575285C11634BC4003FFC9D /* social.c */; }; CF513AC8115B722500662A82 /* appkey.c in Sources */ = {isa = PBXBuildFile; fileRef = CF513ABE115B722500662A82 /* appkey.c */; }; CF513AC9115B722500662A82 /* browse.c in Sources */ = {isa = PBXBuildFile; fileRef = CF513ABF115B722500662A82 /* browse.c */; }; CF513ACA115B722500662A82 /* cmd.c in Sources */ = {isa = PBXBuildFile; fileRef = CF513AC0115B722500662A82 /* cmd.c */; }; CF513ACB115B722500662A82 /* inbox.c in Sources */ = {isa = PBXBuildFile; fileRef = CF513AC2115B722500662A82 /* inbox.c */; }; CF513ACC115B722500662A82 /* search.c in Sources */ = {isa = PBXBuildFile; fileRef = CF513AC3115B722500662A82 /* search.c */; }; CF513ACD115B722500662A82 /* spshell_posix.c in Sources */ = {isa = PBXBuildFile; fileRef = CF513AC4115B722500662A82 /* spshell_posix.c */; }; CF513ACE115B722500662A82 /* spshell.c in Sources */ = {isa = PBXBuildFile; fileRef = CF513AC5115B722500662A82 /* spshell.c */; }; CF513ACF115B722500662A82 /* toplist.c in Sources */ = {isa = PBXBuildFile; fileRef = CF513AC7115B722500662A82 /* toplist.c */; }; CF513AD1115B722E00662A82 /* libspotify.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF513AD0115B722E00662A82 /* libspotify.framework */; }; CF513AF2115B73B900662A82 /* libreadline.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CF513AF1115B73B900662A82 /* libreadline.dylib */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 8DD76FAF0486AB0100D96B5E /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0575285811634BB7003FFC9D /* star.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = star.c; path = ../star.c; sourceTree = SOURCE_ROOT; }; 0575285C11634BC4003FFC9D /* social.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = social.c; path = ../social.c; sourceTree = SOURCE_ROOT; }; 8DD76FB20486AB0100D96B5E /* spshell */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = spshell; sourceTree = BUILT_PRODUCTS_DIR; }; CF513ABE115B722500662A82 /* appkey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = appkey.c; path = ../../appkey.c; sourceTree = SOURCE_ROOT; }; CF513ABF115B722500662A82 /* browse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = browse.c; path = ../browse.c; sourceTree = SOURCE_ROOT; }; CF513AC0115B722500662A82 /* cmd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cmd.c; path = ../cmd.c; sourceTree = SOURCE_ROOT; }; CF513AC1115B722500662A82 /* cmd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cmd.h; path = ../cmd.h; sourceTree = SOURCE_ROOT; }; CF513AC2115B722500662A82 /* inbox.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = inbox.c; path = ../inbox.c; sourceTree = SOURCE_ROOT; }; CF513AC3115B722500662A82 /* search.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = search.c; path = ../search.c; sourceTree = SOURCE_ROOT; }; CF513AC4115B722500662A82 /* spshell_posix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = spshell_posix.c; path = ../spshell_posix.c; sourceTree = SOURCE_ROOT; }; CF513AC5115B722500662A82 /* spshell.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = spshell.c; path = ../spshell.c; sourceTree = SOURCE_ROOT; }; CF513AC6115B722500662A82 /* spshell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = spshell.h; path = ../spshell.h; sourceTree = SOURCE_ROOT; }; CF513AC7115B722500662A82 /* toplist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = toplist.c; path = ../toplist.c; sourceTree = SOURCE_ROOT; }; CF513AD0115B722E00662A82 /* libspotify.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = libspotify.framework; path = /Library/Frameworks/libspotify.framework; sourceTree = ""; }; CF513AF1115B73B900662A82 /* libreadline.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libreadline.dylib; path = usr/lib/libreadline.dylib; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8DD76FAD0486AB0100D96B5E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CF513AD1115B722E00662A82 /* libspotify.framework in Frameworks */, CF513AF2115B73B900662A82 /* libreadline.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 08FB7794FE84155DC02AAC07 /* spshell */ = { isa = PBXGroup; children = ( 08FB7795FE84155DC02AAC07 /* Source */, 1AB674ADFE9D54B511CA2CBB /* Products */, ); name = spshell; sourceTree = ""; }; 08FB7795FE84155DC02AAC07 /* Source */ = { isa = PBXGroup; children = ( CF513ABE115B722500662A82 /* appkey.c */, CF513ABF115B722500662A82 /* browse.c */, CF513AC0115B722500662A82 /* cmd.c */, CF513AC1115B722500662A82 /* cmd.h */, CF513AC2115B722500662A82 /* inbox.c */, CF513AC3115B722500662A82 /* search.c */, CF513AC4115B722500662A82 /* spshell_posix.c */, CF513AC5115B722500662A82 /* spshell.c */, CF513AC6115B722500662A82 /* spshell.h */, CF513AC7115B722500662A82 /* toplist.c */, 0575285811634BB7003FFC9D /* star.c */, 0575285C11634BC4003FFC9D /* social.c */, ); name = Source; sourceTree = ""; }; 1AB674ADFE9D54B511CA2CBB /* Products */ = { isa = PBXGroup; children = ( CF513AF1115B73B900662A82 /* libreadline.dylib */, CF513AD0115B722E00662A82 /* libspotify.framework */, 8DD76FB20486AB0100D96B5E /* spshell */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 8DD76FA90486AB0100D96B5E /* spshell */ = { isa = PBXNativeTarget; buildConfigurationList = 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "spshell" */; buildPhases = ( 0575281D116348DB003FFC9D /* Ensure framework installation */, 8DD76FAB0486AB0100D96B5E /* Sources */, 8DD76FAD0486AB0100D96B5E /* Frameworks */, 8DD76FAF0486AB0100D96B5E /* CopyFiles */, 0575281F116348FB003FFC9D /* Change install name of libspotify */, ); buildRules = ( ); dependencies = ( ); name = spshell; productInstallPath = "$(HOME)/bin"; productName = spshell; productReference = 8DD76FB20486AB0100D96B5E /* spshell */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "spshell" */; compatibilityVersion = "Xcode 3.1"; hasScannedForEncodings = 1; mainGroup = 08FB7794FE84155DC02AAC07 /* spshell */; projectDirPath = ""; projectRoot = ""; targets = ( 8DD76FA90486AB0100D96B5E /* spshell */, ); }; /* End PBXProject section */ /* Begin PBXShellScriptBuildPhase section */ 0575281D116348DB003FFC9D /* Ensure framework installation */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Ensure framework installation"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "if [ ! -e /Library/Frameworks/libspotify.framework ]; then\n echo \"You must install libspotify.framework to /Library/Frameworks.\" >&2\n exit 1\nfi\n\nexit 0"; }; 0575281F116348FB003FFC9D /* Change install name of libspotify */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Change install name of libspotify"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "install_name_tool -change @loader_path/../Frameworks/libspotify.framework/libspotify /Library/Frameworks/libspotify.framework/libspotify $CONFIGURATION_BUILD_DIR/$EXECUTABLE_PATH"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 8DD76FAB0486AB0100D96B5E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CF513AC8115B722500662A82 /* appkey.c in Sources */, CF513AC9115B722500662A82 /* browse.c in Sources */, CF513ACA115B722500662A82 /* cmd.c in Sources */, CF513ACB115B722500662A82 /* inbox.c in Sources */, CF513ACC115B722500662A82 /* search.c in Sources */, CF513ACD115B722500662A82 /* spshell_posix.c in Sources */, CF513ACE115B722500662A82 /* spshell.c in Sources */, CF513ACF115B722500662A82 /* toplist.c in Sources */, 0575285911634BB7003FFC9D /* star.c in Sources */, 0575285D11634BC4003FFC9D /* social.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 1DEB928608733DD80010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; INSTALL_PATH = /usr/local/bin; PRODUCT_NAME = spshell; }; name = Debug; }; 1DEB928708733DD80010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/../../../..\"", ); GCC_MODEL_TUNING = G5; INSTALL_PATH = /usr/local/bin; PRODUCT_NAME = spshell; }; name = Release; }; 1DEB928A08733DD80010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; ONLY_ACTIVE_ARCH = YES; PREBINDING = NO; SDKROOT = macosx10.6; }; name = Debug; }; 1DEB928B08733DD80010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PREBINDING = NO; SDKROOT = macosx10.6; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "spshell" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DEB928608733DD80010E9CD /* Debug */, 1DEB928708733DD80010E9CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "spshell" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DEB928A08733DD80010E9CD /* Debug */, 1DEB928B08733DD80010E9CD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; } ================================================ FILE: libspotify/examples/spshell/playlist.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include "spshell.h" #include "cmd.h" #ifdef WIN32 #define alloca _alloca #else #include #endif static int subscriptions_updated; /** * */ int cmd_update_subscriptions(int argc, char **argv) { int i; sp_playlistcontainer *pc = sp_session_playlistcontainer(g_session); sp_playlist *pl; subscriptions_updated = 1; for (i = 0; i < sp_playlistcontainer_num_playlists(pc); ++i) { switch (sp_playlistcontainer_playlist_type(pc, i)) { case SP_PLAYLIST_TYPE_PLAYLIST: pl = sp_playlistcontainer_playlist(pc, i); sp_playlist_update_subscribers(g_session, pl); break; default: break; } } return 1; } /** * */ int cmd_playlists(int argc, char **argv) { sp_playlistcontainer *pc = sp_session_playlistcontainer(g_session); int i, j, level = 0; sp_playlist *pl; char name[200]; int new = 0; printf("%d entries in the container\n", sp_playlistcontainer_num_playlists(pc)); for (i = 0; i < sp_playlistcontainer_num_playlists(pc); ++i) { switch (sp_playlistcontainer_playlist_type(pc, i)) { case SP_PLAYLIST_TYPE_PLAYLIST: printf("%d. ", i); for (j = level; j; --j) printf("\t"); pl = sp_playlistcontainer_playlist(pc, i); printf("%s", sp_playlist_name(pl)); if(subscriptions_updated) printf(" (%d subscribers)", sp_playlist_num_subscribers(pl)); new = sp_playlistcontainer_get_unseen_tracks(pc, pl, NULL, 0); if (new) printf(" (%d new)", new); printf("\n"); break; case SP_PLAYLIST_TYPE_START_FOLDER: printf("%d. ", i); for (j = level; j; --j) printf("\t"); sp_playlistcontainer_playlist_folder_name(pc, i, name, sizeof(name)); printf("Folder: %s with id %llu\n", name, sp_playlistcontainer_playlist_folder_id(pc, i)); level++; break; case SP_PLAYLIST_TYPE_END_FOLDER: level--; printf("%d. ", i); for (j = level; j; --j) printf("\t"); printf("End folder with id %llu\n", sp_playlistcontainer_playlist_folder_id(pc, i)); break; case SP_PLAYLIST_TYPE_PLACEHOLDER: printf("%d. Placeholder", i); break; } } return 1; } /** * */ static void print_track2(sp_track *track, int i) { printf("%d. %c %s%s %s\n", i, sp_track_is_starred(g_session, track) ? '*' : ' ', sp_track_is_local(g_session, track) ? "local" : " ", sp_track_is_autolinked(g_session, track) ? "autolinked" : " ", sp_track_name(track)); } /** * */ int cmd_playlist(int argc, char **argv) { int index, i, nnew; sp_playlist *playlist; sp_playlistcontainer *pc = sp_session_playlistcontainer(g_session); if (argc < 1) { printf("playlist [playlist index]\n"); return 0; } index = atoi(argv[1]); if (index < 0 || index > sp_playlistcontainer_num_playlists(pc)) { printf("invalid index\n"); return 0; } playlist = sp_playlistcontainer_playlist(pc, index); nnew = sp_playlistcontainer_get_unseen_tracks(pc, playlist, 0, 0); printf("Playlist %s by %s%s%s, %d new tracks\n", sp_playlist_name(playlist), sp_user_display_name(sp_playlist_owner(playlist)), sp_playlist_is_collaborative(playlist) ? " (collaborative)" : "", sp_playlist_has_pending_changes(playlist) ? " with pending changes" : "", nnew ); if (argc == 3) { if (!strcmp(argv[2], "new")) { sp_track **tracks; if (nnew < 0) return 1; tracks = alloca(nnew * sizeof(*tracks)); sp_playlistcontainer_get_unseen_tracks(pc, playlist, tracks, nnew); for (i = 0; i < nnew; i++) { print_track2(tracks[i], i); } return 1; } else if (!strcmp(argv[2], "clear-unseen")) sp_playlistcontainer_clear_unseen_tracks(pc, playlist); } for (i = 0; i < sp_playlist_num_tracks(playlist); ++i) { sp_track *track = sp_playlist_track(playlist, i); print_track2(track, i); } return 1; } /** * */ int cmd_set_autolink(int argc, char **argv) { int index; bool autolink; sp_playlist *playlist; sp_playlistcontainer *pc = sp_session_playlistcontainer(g_session); if (argc < 2) { printf("set autolink [playlist index] [0/1]\n"); return 0; } index = atoi(argv[1]); autolink = atoi(argv[2]); if (index < 0 || index > sp_playlistcontainer_num_playlists(pc)) { printf("invalid index\n"); return 0; } playlist = sp_playlistcontainer_playlist(pc, index); sp_playlist_set_autolink_tracks(playlist, !!autolink); printf("Set autolinking to %s on playlist %s\n", autolink ? "true": "false", sp_playlist_name(playlist)); return 1; } /** * */ int cmd_add_folder(int argc, char **argv) { int index; const char *name; sp_playlistcontainer *pc = sp_session_playlistcontainer(g_session); if (argc < 2) { printf("addfolder [playlist index] [name]\n"); return 0; } index = atoi(argv[1]); name = argv[2]; if (index < 0 || index > sp_playlistcontainer_num_playlists(pc)) { printf("invalid index\n"); return 0; } sp_playlistcontainer_add_folder(pc, index, name); return 1; } static sp_playlist_callbacks pl_update_callbacks; /** * */ struct pl_update_work { int position; int num_tracks; sp_track **tracks; }; static int apply_changes(sp_playlist *pl, struct pl_update_work *puw) { sp_link *l; sp_error err; if(!sp_playlist_is_loaded(pl)) return 1; l = sp_link_create_from_playlist(pl); if(l == NULL) return 1; sp_link_release(l); fprintf(stderr, "Playlist loaded, applying changes ... "); err = sp_playlist_add_tracks(pl, puw->tracks, puw->num_tracks, puw->position, g_session); switch(err) { case SP_ERROR_OK: fprintf(stderr, "OK\n"); break; case SP_ERROR_INVALID_INDATA: fprintf(stderr, "Invalid position\n"); break; case SP_ERROR_PERMISSION_DENIED: fprintf(stderr, "Access denied\n"); break; default: fprintf(stderr, "Other error (should not happen)\n"); break; } return 0; } static void SP_CALLCONV pl_state_change(sp_playlist *pl, void *userdata) { struct pl_update_work *puw = userdata; if(apply_changes(pl, puw)) return; sp_playlist_remove_callbacks(pl, &pl_update_callbacks, puw); sp_playlist_release(pl); cmd_done(); } static sp_playlist_callbacks pl_update_callbacks = { NULL, NULL, NULL, NULL, pl_state_change, }; /** * */ int cmd_playlist_add_track(int argc, char **argv) { sp_link *plink, *tlink; sp_track *t; sp_playlist *pl; int i; struct pl_update_work *puw; if(argc < 4) { printf("add [playlist uri] [position] [track uri] <[track uri]>...\n"); return 1; } plink = sp_link_create_from_string(argv[1]); if (!plink) { fprintf(stderr, "%s is not a spotify link\n", argv[1]); return -1; } if(sp_link_type(plink) != SP_LINKTYPE_PLAYLIST) { fprintf(stderr, "%s is not a playlist link\n", argv[1]); sp_link_release(plink); return -1; } puw = malloc(sizeof(struct pl_update_work)); puw->position = atoi(argv[2]); puw->tracks = malloc(sizeof(sp_track *) * argc - 3); puw->num_tracks = 0; for(i = 0; i < argc - 3; i++) { tlink = sp_link_create_from_string(argv[i + 3]); if(tlink == NULL) { fprintf(stderr, "%s is not a spotify link, skipping\n", argv[i + 3]); continue; } if(sp_link_type(tlink) != SP_LINKTYPE_TRACK) { fprintf(stderr, "%s is not a track link, skipping\n", argv[i + 3]); continue; } t = sp_link_as_track(tlink); sp_track_add_ref(t); puw->tracks[puw->num_tracks++] = t; sp_link_release(tlink); } pl = sp_playlist_create(g_session, plink); if(!apply_changes(pl, puw)) { // Changes applied directly, we're done sp_playlist_release(pl); sp_link_release(plink); return 1; } fprintf(stderr, "Playlist not yet loaded, waiting...\n"); sp_playlist_add_callbacks(pl, &pl_update_callbacks, puw); sp_link_release(plink); return 0; } static const char *offlinestatus[] = { "None", // SP_PLAYLIST_OFFLINE_STATUS_NO "Synchronized", // SP_PLAYLIST_OFFLINE_STATUS_YES "Downloading", // SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING "Waiting", // SP_PLAYLIST_OFFLINE_STATUS_WAITING }; int cmd_playlist_offline(int argc, char **argv) { sp_link *plink; sp_playlist *pl; int on; if (argc == 2 && !strcmp(argv[1], "status")) { printf("Offline status\n"); printf(" %d tracks to sync\n", sp_offline_tracks_to_sync(g_session)); printf(" %d offline playlists in total\n", sp_offline_num_playlists(g_session)); return 1; } if (argc != 3) { printf("offline status | \n"); return 1; } plink = sp_link_create_from_string(argv[1]); if (!plink) { fprintf(stderr, "%s is not a spotify link\n", argv[1]); return -1; } if (sp_link_type(plink) != SP_LINKTYPE_PLAYLIST) { fprintf(stderr, "%s is not a playlist link\n", argv[1]); sp_link_release(plink); return -1; } pl = sp_playlist_create(g_session, plink); if (argc == 3) { if (!strcasecmp(argv[2], "on")) on = 1; else if (!strcasecmp(argv[2], "off")) on = 0; else { fprintf(stderr, "Invalid mode: %s\n", argv[2]); return -1; } sp_playlist_set_offline_mode(g_session, pl, on); } else { sp_playlist_offline_status s; s = sp_playlist_get_offline_status(g_session, pl); printf("Offline status for %s (%s)\n", argv[1], sp_playlist_name(pl)); printf(" Status: %s\n", offlinestatus[s]); if (s == SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING) printf(" %d%% Complete\n", sp_playlist_get_offline_download_completed(g_session, pl)); } sp_playlist_release(pl); sp_link_release(plink); return 1; } ================================================ FILE: libspotify/examples/spshell/scrobbling.c ================================================ /** * Copyright (c) 2006-2012 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "spshell.h" #include void print_scrobbling_state(sp_scrobbling_state state) { if (state == SP_SCROBBLING_STATE_LOCAL_ENABLED) printf("locally overridden, enabled"); else if (state == SP_SCROBBLING_STATE_LOCAL_DISABLED) printf("locally overridden, disabled"); else if (state == SP_SCROBBLING_STATE_GLOBAL_ENABLED) printf("globally set, enabled"); else if (state == SP_SCROBBLING_STATE_GLOBAL_DISABLED) printf("globally set, disabled"); else printf(""); } int cmd_facebook_scrobbling(int argc, char **argv) { if(argc != 2) { fprintf(stderr, "facebook scrobbling local override enable|disable|unset\n"); return -1; } sp_scrobbling_state state = SP_SCROBBLING_STATE_USE_GLOBAL_SETTING; if (strcmp(argv[1], "unset")) { // not "unset" bool enable = !strcmp(argv[1], "enable"); state = enable ? SP_SCROBBLING_STATE_LOCAL_ENABLED : SP_SCROBBLING_STATE_LOCAL_DISABLED; } sp_session_set_scrobbling(g_session, SP_SOCIAL_PROVIDER_FACEBOOK, state); return 1; } int cmd_is_facebook_scrobbling(int argc, char **argv) { printf("facebook scrobbling state is "); sp_scrobbling_state state; sp_session_is_scrobbling(g_session, SP_SOCIAL_PROVIDER_FACEBOOK, &state); print_scrobbling_state(state); printf("\n"); return 1; } int cmd_spotify_social(int argc, char **argv) { if(argc != 2) { fprintf(stderr, "spotify social local override enable|disable|unset\n"); return -1; } sp_scrobbling_state state = SP_SCROBBLING_STATE_USE_GLOBAL_SETTING; if (strcmp(argv[1], "unset")) { // not "unset" bool enable = !strcmp(argv[1], "enable"); state = enable ? SP_SCROBBLING_STATE_LOCAL_ENABLED : SP_SCROBBLING_STATE_LOCAL_DISABLED; } sp_session_set_scrobbling(g_session, SP_SOCIAL_PROVIDER_SPOTIFY, state); return 1; } int cmd_is_spotify_social(int argc, char **argv) { printf("spotify social scrobbling state is "); sp_scrobbling_state state; sp_session_is_scrobbling(g_session, SP_SOCIAL_PROVIDER_SPOTIFY, &state); print_scrobbling_state(state); printf("\n"); return 1; } int cmd_set_lastfm_scrobbling_credentials(int argc, char **argv) { if(argc != 3) { fprintf(stderr, "lastfm_scrobbling_credentials username password\n"); return -1; } sp_session_set_social_credentials(g_session, SP_SOCIAL_PROVIDER_LASTFM, argv[1], argv[2]); return 1; } int cmd_lastfm_scrobbling(int argc, char **argv) { if(argc != 2) { fprintf(stderr, "lastfm_scrobbling enable|disable\n"); return -1; } bool enable = !strcmp(argv[1], "enable"); sp_scrobbling_state state = enable ? SP_SCROBBLING_STATE_LOCAL_ENABLED : SP_SCROBBLING_STATE_LOCAL_DISABLED; sp_session_set_scrobbling(g_session, SP_SOCIAL_PROVIDER_LASTFM, state); return 1; } int cmd_is_lastfm_scrobbling(int argc, char **argv) { printf("lastfm scrobbling state is "); sp_scrobbling_state state; sp_session_is_scrobbling(g_session, SP_SOCIAL_PROVIDER_LASTFM, &state); if (state == SP_SCROBBLING_STATE_LOCAL_ENABLED) printf("enabled"); else if (state == SP_SCROBBLING_STATE_LOCAL_DISABLED) printf("disabled"); else printf(""); printf("\n"); return 1; } extern int cmd_private_session(int argc, char** argv) { if(argc != 2) { fprintf(stderr, "private_session enable|disable\n"); return -1; } sp_session_set_private_session(g_session, !strcmp(argv[1], "enable")); return 1; } extern int cmd_is_private_session(int argc, char** argv) { printf("private_session is "); printf( sp_session_is_private_session(g_session) ? "enabled" : "disabled"); printf("\n"); return 1; } ================================================ FILE: libspotify/examples/spshell/search.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include "spshell.h" #include "cmd.h" /** * Print the given album metadata * * @param album The album object */ static void print_album(sp_album *album) { printf(" Album \"%s\" (%d)\n", sp_album_name(album), sp_album_year(album)); } /** * Print the given artist metadata * * @param artist The artist object */ static void print_artist(sp_artist *artist) { printf(" Artist \"%s\"\n", sp_artist_name(artist)); } /** * Print the given search result with as much information as possible * * @param search The search result */ static void print_search(sp_search *search) { int i; printf("Query : %s\n", sp_search_query(search)); printf("Did you mean : %s\n", sp_search_did_you_mean(search)); printf("Tracks in total: %d\n", sp_search_total_tracks(search)); puts(""); for (i = 0; i < sp_search_num_tracks(search); ++i) print_track(sp_search_track(search, i)); puts(""); for (i = 0; i < sp_search_num_albums(search); ++i) print_album(sp_search_album(search, i)); puts(""); for (i = 0; i < sp_search_num_artists(search); ++i) print_artist(sp_search_artist(search, i)); puts(""); for (i = 0; i < sp_search_num_playlists(search); ++i) { // print some readily available metadata, the rest will // be available from the sp_playlist object loaded through // sp_search_playlist(). printf(" Playlist \"%s\"\n", sp_search_playlist_name(search, i)); } } /** * Callback for libspotify * * @param browse The browse result object that is now done * @param userdata The opaque pointer given to sp_artistbrowse_create() */ static void SP_CALLCONV search_complete(sp_search *search, void *userdata) { if (sp_search_error(search) == SP_ERROR_OK) print_search(search); else fprintf(stderr, "Failed to search: %s\n", sp_error_message(sp_search_error(search))); sp_search_release(search); cmd_done(); } /** * */ static void search_usage(void) { fprintf(stderr, "Usage: search \n"); } /** * */ int cmd_search(int argc, char **argv) { char query[1024]; int i; if (argc < 2) { search_usage(); return -1; } query[0] = 0; for(i = 1; i < argc; i++) snprintf(query + strlen(query), sizeof(query) - strlen(query), "%s%s", i == 1 ? "" : " ", argv[i]); sp_search_create(g_session, query, 0, 100, 0, 100, 0, 100, 0, 100, SP_SEARCH_STANDARD, &search_complete, NULL); return 0; } /** * */ int cmd_whatsnew(int argc, char **argv) { sp_search_create(g_session, "tag:new", 0, 0, 0, 250, 0, 0, 0, 0, SP_SEARCH_STANDARD, &search_complete, NULL); return 0; } ================================================ FILE: libspotify/examples/spshell/spshell.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include "spshell.h" #include "cmd.h" sp_session *g_session; void (*metadata_updated_fn)(void); int is_logged_out; int log_to_stderr; int g_selftest; /** * This callback is called when the user was logged in, but the connection to * Spotify was dropped for some reason. * * @sa sp_session_callbacks#connection_error */ static void SP_CALLCONV connection_error(sp_session *session, sp_error error) { fprintf(stderr, "Connection to Spotify failed: %s\n", sp_error_message(error)); } /** * This callback is called when an attempt to login has succeeded or failed. * * @sa sp_session_callbacks#logged_in */ static void SP_CALLCONV logged_in(sp_session *session, sp_error error) { sp_user *me; const char *display_name, *username; int cc; if (SP_ERROR_OK != error) { fprintf(stderr, "failed to log in to Spotify: %s\n", sp_error_message(error)); sp_session_release(session); exit(4); } // Let us print the nice message... me = sp_session_user(session); display_name = (sp_user_is_loaded(me) ? sp_user_display_name(me) : sp_user_canonical_name(me)); username = sp_session_user_name (session); cc = sp_session_user_country(session); fprintf(stderr, "Logged in to Spotify as user %s [%s] (registered in country: %c%c)\n", username, display_name, cc >> 8, cc & 0xff); #if WITH_TEST_COMMAND if(g_selftest) cmd_test(0, NULL); else #endif start_prompt(); } /** * This callback is called when the session has logged out of Spotify. * * @sa sp_session_callbacks#logged_out */ static void SP_CALLCONV logged_out(sp_session *session) { is_logged_out = 1; // Will exit mainloop } #ifdef SP_LIBSPOTIFY_WITH_SCROBBLING /** * This callback is called when there is a scrobble error. * * @sa sp_session_callbacks#scrobble_error */ static void SP_CALLCONV scrobble_error(sp_session* session, sp_error error) { fprintf(stderr, "Scrobble failure: %d\n", error); } /** * Called when there is a change in the private session mode * * @param[in] session Session * @param[in] isPrivate True if in private session, false otherwhise */ static void SP_CALLCONV private_session_mode_changed(sp_session *session, bool is_private) { printf("private session mode changed: %d\n", is_private); } #endif /** * This callback is called when the session have recieved a credential * that could be stored safely on disk * * @sa sp_session_callbacks#credentials_blob_updated */ static void SP_CALLCONV credentials_blob_updated(sp_session *session, const char *blob) { printf("blob for storage: %s\n", blob); } /** * This callback is called for log messages. * * @sa sp_session_callbacks#log_message */ static void SP_CALLCONV log_message(sp_session *session, const char *data) { if (log_to_stderr) fprintf(stderr, "%s", data); } /** * Callback called when libspotify has new metadata available * * Not used in this example (but available to be able to reuse the session.c file * for other examples.) * * @sa sp_session_callbacks#metadata_updated */ static void SP_CALLCONV metadata_updated(sp_session *sess) { if(metadata_updated_fn) metadata_updated_fn(); #if WITH_TEST_COMMAND if(g_selftest) test_process(); #endif } /** * */ static void SP_CALLCONV offline_status_updated(sp_session *sess) { sp_offline_sync_status status; sp_offline_sync_get_status(sess, &status); if(status.syncing) { printf("Offline status: queued:%d:%zd done:%d:%zd copied:%d:%zd nocopy:%d err:%d\n", status.queued_tracks, (size_t)status.queued_bytes, status.done_tracks, (size_t)status.done_bytes, status.copied_tracks, (size_t)status.copied_bytes, status.willnotcopy_tracks, status.error_tracks); } else { printf("Offline status: Idle\n"); } } /** * Session callbacks */ static sp_session_callbacks callbacks; /** * */ int spshell_init(const char *username, const char *password, const char *blob, int selftest) { sp_session_config config; sp_error error; sp_session *session; /// The application key is specific to each project, and allows Spotify /// to produce statistics on how our service is used. extern const char g_appkey[]; /// The size of the application key. extern const size_t g_appkey_size; g_selftest = selftest; memset(&config, 0, sizeof(config)); // Always do this. It allows libspotify to check for // header/library inconsistencies. config.api_version = SPOTIFY_API_VERSION; // The path of the directory to store the cache. This must be specified. // Please read the documentation on preferred values. config.cache_location = selftest ? "" : "tmp"; // The path of the directory to store the settings. // This must be specified. // Please read the documentation on preferred values. config.settings_location = selftest ? "" : "tmp"; // The key of the application. They are generated by Spotify, // and are specific to each application using libspotify. config.application_key = g_appkey; config.application_key_size = g_appkey_size; // This identifies the application using some // free-text string [1, 255] characters. config.user_agent = "spshell"; // Register the callbacks. callbacks.logged_in = logged_in; callbacks.logged_out = logged_out; callbacks.metadata_updated = metadata_updated; callbacks.connection_error = connection_error; callbacks.notify_main_thread = notify_main_thread; #if WITH_TEST_COMMAND callbacks.music_delivery = music_delivery; callbacks.play_token_lost = play_token_lost; callbacks.end_of_track = end_of_track; #endif callbacks.log_message = log_message; callbacks.offline_status_updated = offline_status_updated; callbacks.credentials_blob_updated = credentials_blob_updated; #ifdef SP_LIBSPOTIFY_WITH_SCROBBLING callbacks.scrobble_error = scrobble_error; callbacks.private_session_mode_changed = private_session_mode_changed; #endif config.callbacks = &callbacks; error = sp_session_create(&config, &session); if (SP_ERROR_OK != error) { fprintf(stderr, "failed to create session: %s\n", sp_error_message(error)); return 2; } // Login using the credentials given on the command line. if (username == NULL) { char reloginname[256]; if (sp_session_relogin(session) == SP_ERROR_NO_CREDENTIALS) { fprintf(stderr, "No stored credentials\n"); return 3; } sp_session_remembered_user(session, reloginname, sizeof(reloginname)); fprintf(stderr, "Trying to relogin as user %s\n", reloginname); } else { sp_session_login(session, username, password, 1, blob); } g_session = session; return 0; } /** * */ int cmd_logout(int argc, char **argv) { if(argc == 2 && !strcmp(argv[1], "permanent")) { fprintf(stderr, "Dropping stored credentials\n"); sp_session_forget_me(g_session); } sp_session_logout(g_session); return 0; } /** * */ void test_finished(void) { cmd_logout(0, NULL); } /** * */ int cmd_log(int argc, char **argv) { if(argc != 2) { fprintf(stderr, "log enable|disable\n"); return -1; } log_to_stderr = !strcmp(argv[1], "enable"); return 1; } ================================================ FILE: libspotify/examples/spshell/spshell.h ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #ifndef SPSHELL_H__ #define SPSHELL_H__ #include #ifndef WIN32 #include #endif #include #include #define WITH_TEST_COMMAND 1 #if WIN32 #include #define snprintf sprintf_s #define strcasecmp lstrcmp #endif #ifndef PRIx64 #define PRIx64 "llx" #endif #if USE_STRCMP #define strcasecmp strcmp #endif extern sp_session *g_session; extern void (*metadata_updated_fn)(void); extern int spshell_init(const char *username, const char *password, const char *blob, int selftest); extern void SP_CALLCONV notify_main_thread(sp_session *session); extern void start_prompt(void); extern sp_uint64 get_ts(void); #if WITH_TEST_COMMAND extern void test_finished(void); extern void test_process(void); extern void SP_CALLCONV end_of_track(sp_session *s); extern int SP_CALLCONV music_delivery(sp_session *s, const sp_audioformat *fmt, const void *frames, int num_frames); extern void SP_CALLCONV play_token_lost(sp_session *s); #endif #endif // SPSHELL_H__ ================================================ FILE: libspotify/examples/spshell/spshell_posix.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include #include #include #include #include #include #include "spshell.h" #include "cmd.h" /// Set when libspotify want to process events static int notify_events; /// Synchronization mutex to protect various shared data static pthread_mutex_t notify_mutex; /// Synchronization condition variable for the main thread static pthread_cond_t notify_cond; /// Synchronization condition variable to disable prompt temporarily static pthread_cond_t prompt_cond; /// Command line to execute static char *cmdline; /// static int show_prompt; extern int is_logged_out; extern int g_selftest; /** * */ static void *promptloop(void *aux) { pthread_mutex_lock(¬ify_mutex); while(1) { char *l; while(show_prompt == 0) pthread_cond_wait(&prompt_cond, ¬ify_mutex); pthread_mutex_unlock(¬ify_mutex); l = readline("> "); pthread_mutex_lock(¬ify_mutex); show_prompt = 0; cmdline = l; pthread_cond_signal(¬ify_cond); } return NULL; } /** * */ void start_prompt(void) { static pthread_t id; if (id) return; show_prompt = 1; pthread_create(&id, NULL, promptloop, NULL); } /** * */ static void trim(char *buf) { size_t l = strlen(buf); while(l > 0 && buf[l - 1] < 32) buf[--l] = 0; } /** * */ int main(int argc, char **argv) { const char *username = NULL; const char *blob = NULL; const char *password = NULL; int selftest = 0; char username_buf[256]; int r; int next_timeout = 0; int ch; printf("Using libspotify %s\n", sp_build_id()); r = 0; while ((ch = getopt(argc, argv, "tb:u:p:")) != -1) { switch (ch) { case 'u': username = optarg; break; case 'p': password = optarg; break; case 'b': blob = optarg; r +=2; break; case 't': selftest = 1; r++; break; default: break; } } if (username == NULL) { printf("Username (just press enter to login with stored credentials): "); fflush(stdout); fgets(username_buf, sizeof(username_buf), stdin); trim(username_buf); if(username_buf[0] == 0) { username = NULL; } else { username = username_buf; } } // If a username was supplied but no blob, prompt for password if (username != NULL && password == NULL && blob == NULL) password = getpass("Password: "); pthread_mutex_init(¬ify_mutex, NULL); pthread_cond_init(¬ify_cond, NULL); pthread_cond_init(&prompt_cond, NULL); if ((r = spshell_init(username, password, blob, selftest)) != 0) exit(r); pthread_mutex_lock(¬ify_mutex); while(!is_logged_out) { // Release prompt if (next_timeout == 0) { while(!notify_events && !cmdline) pthread_cond_wait(¬ify_cond, ¬ify_mutex); } else { struct timespec ts; #if _POSIX_TIMERS > 0 clock_gettime(CLOCK_REALTIME, &ts); #else struct timeval tv; gettimeofday(&tv, NULL); TIMEVAL_TO_TIMESPEC(&tv, &ts); #endif ts.tv_sec += next_timeout / 1000; ts.tv_nsec += (next_timeout % 1000) * 1000000; if(ts.tv_nsec > 1000000000) { ts.tv_sec ++; ts.tv_nsec -= 1000000000; } while(!notify_events && !cmdline) { if(pthread_cond_timedwait(¬ify_cond, ¬ify_mutex, &ts)) break; } } // Process input from prompt if(cmdline) { char *l = cmdline; cmdline = NULL; pthread_mutex_unlock(¬ify_mutex); cmd_exec_unparsed(l); free(l); pthread_mutex_lock(¬ify_mutex); } // Process libspotify events notify_events = 0; pthread_mutex_unlock(¬ify_mutex); do { sp_session_process_events(g_session, &next_timeout); } while (next_timeout == 0); if(g_selftest) test_process(); pthread_mutex_lock(¬ify_mutex); } printf("Logged out\n"); sp_session_release(g_session); printf("Exiting...\n"); return 0; } /** * */ void cmd_done(void) { pthread_mutex_lock(¬ify_mutex); show_prompt = 1; pthread_cond_signal(&prompt_cond); pthread_mutex_unlock(¬ify_mutex); } /** * */ void notify_main_thread(sp_session *session) { pthread_mutex_lock(¬ify_mutex); notify_events = 1; pthread_cond_signal(¬ify_cond); pthread_mutex_unlock(¬ify_mutex); } /** * */ sp_uint64 get_ts(void) { struct timeval tv; gettimeofday(&tv, NULL); return (int64_t)tv.tv_sec * 1000LL + (tv.tv_usec / 1000); } ================================================ FILE: libspotify/examples/spshell/spshell_win32.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "spshell.h" #include "cmd.h" static HANDLE events[2]; static int enable_console; static char console_buf[1024]; static int console_ptr; extern int is_logged_out; extern int g_selftest; /** * Very simplistic console input */ static void console_input(void) { TCHAR inp; DWORD read; INPUT_RECORD rec; if(!ReadConsoleInput(events[1], &rec, 1, &read)) return; if(read == 0) return; if(rec.EventType != KEY_EVENT) return; if(!rec.Event.KeyEvent.bKeyDown) return; inp = rec.Event.KeyEvent.uChar.AsciiChar; switch(inp) { case 8: if(console_ptr > 0) { console_ptr--; printf("\b \b"); } break; case 13: printf("\n\r"); console_buf[console_ptr++] = 0; console_ptr = 0; enable_console = 0; cmd_exec_unparsed(console_buf); break; default: if(console_ptr < sizeof(console_buf) - 1) { printf("%c", (char)inp); console_buf[console_ptr++] = inp; } break; } } static void trim(char *buf) { size_t l = strlen(buf); while(l > 0 && buf[l - 1] < 32) buf[--l] = 0; } int __cdecl main(int argc, char **argv) { const char *username = argc > 1 ? argv[1] : NULL; const char *blob = argc > 2 ? argv[2] : NULL; const char *password = NULL; int selftest = argc > 3 ? !strcmp(argv[3], "selftest") : 0; char username_buf[256]; char password_buf[256]; int r; int next_timeout = 0; DWORD ev; DWORD mode; events[0] = CreateEvent(NULL, FALSE, FALSE, NULL); events[1] = GetStdHandle(STD_INPUT_HANDLE); printf("Using libspotify %s\n", sp_build_id()); if (username == NULL) { printf("Username: (just press enter to login with stored credentials): "); fflush(stdout); fgets(username_buf, sizeof(username_buf), stdin); trim(username_buf); if(username_buf[0] == 0) { username = NULL; } else { username = username_buf; } } // If a username was supplied but no blob, prompt for password if (username != NULL && blob == NULL) { printf("Password: "); fflush(stdout); if (!GetConsoleMode(events[1], &mode) || !SetConsoleMode(events[1], mode & ~ENABLE_ECHO_INPUT)) { printf("Unable to set console mode err=%d\n", GetLastError()); exit(1); } fgets(password_buf, sizeof(password_buf), stdin); trim(password_buf); password = password_buf; printf("\r\n"); } if ((r = spshell_init(username, password, blob, selftest)) != 0) exit(r); if (!SetConsoleMode(events[1], ENABLE_PROCESSED_INPUT)) { printf("Unable to set console mode err=%d\n", GetLastError()); exit(1); } while(!is_logged_out) { ev = WaitForMultipleObjects(1 + enable_console, events, FALSE, next_timeout > 0 ? next_timeout : INFINITE); switch (ev) { case WAIT_OBJECT_0 + 0: case WAIT_TIMEOUT: do { sp_session_process_events(g_session, &next_timeout); } while (next_timeout == 0); if(g_selftest) test_process(); break; case WAIT_OBJECT_0 + 1: console_input(); break; } } printf("Logged out\n"); sp_session_release(g_session); printf("Exiting...\n"); return 0; } /** * */ void cmd_done(void) { enable_console = 1; printf("> "); fflush(stdout); } /** * */ void start_prompt(void) { cmd_done(); } /** * */ void SP_CALLCONV notify_main_thread(sp_session *session) { SetEvent(events[0]); } /** * */ sp_uint64 get_ts(void) { return GetTickCount(); } ================================================ FILE: libspotify/examples/spshell/star.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "spshell.h" #include "cmd.h" /** * */ static void star_usage(const char *prefix) { fprintf(stderr, "Usage: %sstar \n", prefix); } /** * */ static int dostar(int argc, char **argv, int set) { sp_link *link; sp_track *track; if (argc != 2) { star_usage(set ? "" : "un"); return -1; } link = sp_link_create_from_string(argv[1]); if (!link) { fprintf(stderr, "Not a spotify link\n"); return -1; } if (sp_link_type(link) != SP_LINKTYPE_TRACK) { fprintf(stderr, "Not a track link\n"); sp_link_release(link); return -1; } track = sp_link_as_track(link); sp_track_set_starred(g_session, &track, 1, set); sp_link_release(link); return -1; } /** * */ int cmd_star(int argc, char **argv) { return dostar(argc, argv, 1); } /** * */ int cmd_unstar(int argc, char **argv) { return dostar(argc, argv, 0); } /** * */ int cmd_starred(int argc, char **argv) { sp_playlist *starred; if (argc > 1) { starred = sp_session_starred_for_user_create(g_session, argv[1]); } else { starred = sp_session_starred_create(g_session); } if (starred) { browse_playlist(starred); } else { printf("Starred not loaded\n"); } return 1; } ================================================ FILE: libspotify/examples/spshell/test.c ================================================ /** * Copyright (c) 2006-2011 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include #include #if __APPLE__ || __linux__ #include #endif // __APPLE__ || __linux__ #include "spshell.h" #include "cmd.h" #define STRINGIFY(s) TOSTRING(s) #define TOSTRING(s) #s enum msglevel { MSG_ERROR, MSG_INFO, MSG_OK, }; #if WIN32 const char *dec_by_level[] = { "", "", "" }; static const char *decorate_clr = ""; #else const char *dec_by_level[] = { "\033[31m", "\033[36m", "\033[32m" }; static const char *decorate_clr = "\033[0m"; #endif typedef struct Test { // Initialized by TEST_DECL const char *title; int maxtime; int arg1; int arg2; int arg3; // Other members struct Test *next; sp_uint64 start; int duration; int done; } Test; static struct Test *active_tests; #define TEST_DECL(title,maxtime) Test title = { STRINGIFY(title), maxtime } #define TEST_DECL3(title,maxtime, a) Test title = { STRINGIFY(title), maxtime, a } #define TEST_DECL4(title,maxtime, a,b) Test title = { STRINGIFY(title), maxtime, a,b } #define TEST_DECL5(title,maxtime, a,b,c) Test title = { STRINGIFY(title), maxtime, a,b,c } void test_activate(struct Test *t) { t->next = active_tests; active_tests = t; } void test_list_active(void) { struct Test *t; for(t = active_tests; t != NULL; t = t->next) printf("%s\n", t->title); } void output(enum msglevel level, const char *fmt, ...) { char msg[512]; va_list ap; va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); printf("%s%s%s\n", dec_by_level[level], msg, decorate_clr); } void test_deactivate(const struct Test * const t) { struct Test *l, **p = &active_tests; while((l = *p) != NULL) { if(t == l) { *p = l->next; return; } p = &l->next; } output(MSG_ERROR, "Test %s not linked", t->title); abort(); } static int test_done(Test *t, int backend_time) { t->done++; if(t->done > 1) { output(MSG_ERROR, "%s: Finished multiple times (%d)", t->title, t->done); abort(); return 1; } test_deactivate(t); t->duration = (int)(get_ts() - t->start); if(t->duration > t->maxtime * 1000) { char trailer[100]; if(backend_time != -1) { snprintf(trailer, sizeof(trailer), ", %dms waiting for backend", backend_time); } else { trailer[0] = 0; } output(MSG_ERROR, "%s: %d Time exceeded, limit is %dms%s", t->title, t->duration, t->maxtime * 1000, trailer); return 1; } return 0; } static void test_ok(Test *t) { if(test_done(t, -1)) return; output(MSG_OK, "%s: %d OK", t->title, t->duration); } static void test_ok_backend_duration(Test *t, int backend_duration) { char trailer[100]; if(test_done(t, backend_duration)) return; if(backend_duration == -1) strcpy(trailer, "Loaded from cache"); else snprintf(trailer, sizeof(trailer), "%d ms backend request delay", backend_duration); output(MSG_OK, "%s: %d OK, %s", t->title, t->duration, trailer); } static void test_report(Test *t, const char *fmt, ...) { char msg[512]; va_list ap; int level; if(test_done(t, -1)) return; level = MSG_ERROR; va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); output(level, "%s: %d %s", t->title, t->duration, msg); } #define info_report(fmt, ...) output(MSG_INFO, fmt, ##__VA_ARGS__) static void test_start(Test *t) { t->start = get_ts(); test_activate(t); } /***************************************************************** * URI helpers */ static sp_artist *artist_from_uri(const char *uri) { sp_artist *a; sp_link *l = sp_link_create_from_string(uri); if(sp_link_type(l) != SP_LINKTYPE_ARTIST) { sp_link_release(l); return NULL; } a = sp_link_as_artist(l); sp_artist_add_ref(a); sp_link_release(l); return a; } static sp_album *album_from_uri(const char *uri) { sp_album *a; sp_link *l = sp_link_create_from_string(uri); if(sp_link_type(l) != SP_LINKTYPE_ALBUM) { sp_link_release(l); return NULL; } a = sp_link_as_album(l); sp_album_add_ref(a); sp_link_release(l); return a; } static sp_track *track_from_uri(const char *uri) { sp_track *t; sp_link *l = sp_link_create_from_string(uri); if(sp_link_type(l) != SP_LINKTYPE_TRACK) { sp_link_release(l); return NULL; } t = sp_link_as_track(l); sp_track_add_ref(t); sp_link_release(l); return t; } /***************************************************************** * Search */ TEST_DECL(search1, 3); TEST_DECL(search2, 3); TEST_DECL(did_you_mean, 3); static void SP_CALLCONV search1_cb(sp_search *result, void *userdata) { if(!sp_search_is_loaded(result)) test_report(userdata, "Result not loaded"); else if(sp_search_error(result) != SP_ERROR_OK) test_report(userdata, "%s", sp_error_message(sp_search_error(result))); else if(sp_search_num_tracks(result) != 2) test_report(userdata, "Expected %d tracks got %d", 2, sp_search_num_tracks(result)); else if(sp_search_total_tracks(result) < sp_search_num_tracks(result)) test_report(userdata, "Total tracks (%d) less than number of tracks (%d)", sp_search_total_tracks(result), sp_search_num_tracks(result)); else if(sp_search_num_albums(result) != 4) test_report(userdata, "Expected %d albums got %d", 4, sp_search_num_albums(result)); else if(sp_search_total_albums(result) < sp_search_num_albums(result)) test_report(userdata, "Total albums (%d) less than number of albums (%d)", sp_search_total_albums(result), sp_search_num_albums(result)); else if(sp_search_num_artists(result) != 6) test_report(userdata, "Expected %d artists got %d", 6, sp_search_num_artists(result)); else if(sp_search_total_artists(result) < sp_search_num_artists(result)) test_report(userdata, "Total artists (%d) less than number of artists (%d)", sp_search_total_artists(result), sp_search_num_artists(result)); else if(sp_search_num_playlists(result) != 8) test_report(userdata, "Expected %d playlists got %d", 8, sp_search_num_playlists(result)); else if(sp_search_total_playlists(result) < sp_search_num_playlists(result)) test_report(userdata, "Total playlists (%d) less than number of playlists (%d)", sp_search_total_playlists(result), sp_search_num_playlists(result)); else { if (!sp_search_playlist_name(result, 1)) { test_report(userdata, "Expected a name for playlist #1"); } else test_ok(userdata); } sp_search_release(result); } static void SP_CALLCONV search2_cb(sp_search *result, void *userdata) { if(!sp_search_is_loaded(result)) test_report(userdata, "Result not loaded"); else if(sp_search_error(result) != SP_ERROR_OK) test_report(userdata, "%s", sp_error_message(sp_search_error(result))); else test_ok(userdata); sp_search_release(result); } static void SP_CALLCONV did_you_mean_cb(sp_search *result, void *userdata) { if(!sp_search_is_loaded(result)) test_report(userdata, "Result not loaded"); else if(sp_search_error(result) != SP_ERROR_OK) test_report(userdata, "%s", sp_error_message(sp_search_error(result))); else if(strcmp(sp_search_did_you_mean(result), "madonna")) test_report(userdata, "Expected '%s' but got '%s'", "madonna", sp_search_did_you_mean(result)); else test_ok(userdata); sp_search_release(result); } static void search_test(void) { test_start(&search1); sp_search_create(g_session, "madonna", 1,2,3,4,5,6,7,8, SP_SEARCH_STANDARD, search1_cb, &search1); test_start(&search2); sp_search_create(g_session, "madonna", 0,250,0,250,0,250, 0, 0, SP_SEARCH_STANDARD, search2_cb, &search2); test_start(&did_you_mean); sp_search_create(g_session, "madonnnna", 0,250,0,250,0,250, 0, 0, SP_SEARCH_STANDARD, did_you_mean_cb, &did_you_mean); } /***************************************************************** * Album browse */ TEST_DECL(browse_50th_law, 3); static void SP_CALLCONV album_cb(sp_albumbrowse *result, void *userdata) { if(!sp_albumbrowse_is_loaded(result)) test_report(userdata, "Result not loaded"); else if(sp_albumbrowse_error(result) != SP_ERROR_OK) test_report(userdata, "%s", sp_error_message(sp_albumbrowse_error(result))); else if(sp_albumbrowse_num_tracks(result) != 11) test_report(userdata, "Expected 11 tracks but got %d", sp_albumbrowse_num_tracks(result)); else if(strcmp("Introduction - Excerpt", sp_track_name(sp_albumbrowse_track(result, 0)))) test_report(userdata, "Expected track #1 to be %s but got %s", "Introduction - Excerpt", sp_track_name(sp_albumbrowse_track(result, 0))); else if(strcmp("Confront Your Mortality – the Sublime - Excerpt", sp_track_name(sp_albumbrowse_track(result, 10)))) test_report(userdata, "Expected track #11 to be %s but got %s", "Confront Your Mortality – the Sublime - Excerpt", sp_track_name(sp_albumbrowse_track(result, 10))); else test_ok_backend_duration(userdata, sp_albumbrowse_backend_request_duration(result)); sp_albumbrowse_release(result); } static void albumbrowse_test(void) { sp_album *alb; test_start(&browse_50th_law); alb = album_from_uri("spotify:album:1kSXIcMv2voPwcjaJiUoC5"); sp_albumbrowse_create(g_session, alb, album_cb, &browse_50th_law); sp_album_release(alb); } /***************************************************************** * Artist browse no albums */ TEST_DECL(browse_david_guetta, 3); TEST_DECL(browse_adele, 3); TEST_DECL(browse_veronica_maggio, 3); TEST_DECL(browse_rihanna, 3); TEST_DECL(browse_elvis, 3); static void SP_CALLCONV no_albums_cb(sp_artistbrowse *result, void *userdata) { if(!sp_artistbrowse_is_loaded(result)) test_report(userdata, "Result not loaded"); else if(sp_artistbrowse_error(result) != SP_ERROR_OK) test_report(userdata, "%s", sp_error_message(sp_artistbrowse_error(result))); else if(sp_artistbrowse_num_tracks(result)) test_report(userdata, "Expected 0 tracks but got %d", sp_artistbrowse_num_tracks(result)); else if(sp_artistbrowse_num_albums(result)) test_report(userdata, "Expected 0 albums but got %d", sp_artistbrowse_num_albums(result)); else test_ok_backend_duration(userdata, sp_artistbrowse_backend_request_duration(result)); sp_artistbrowse_release(result); } static void artistbrowse_test(void) { sp_artist *art; test_start(&browse_david_guetta); test_start(&browse_adele); test_start(&browse_veronica_maggio); test_start(&browse_rihanna); test_start(&browse_elvis); art = artist_from_uri("spotify:artist:1Cs0zKBU1kc0i8ypK3B9ai"); sp_artistbrowse_create(g_session, art, SP_ARTISTBROWSE_NO_ALBUMS, no_albums_cb, &browse_david_guetta); sp_artist_release(art); art = artist_from_uri("spotify:artist:4dpARuHxo51G3z768sgnrY"); sp_artistbrowse_create(g_session, art, SP_ARTISTBROWSE_NO_ALBUMS, no_albums_cb, &browse_adele); sp_artist_release(art); art = artist_from_uri("spotify:artist:2OIWxN9xUhgUHkeUCWCaNs"); sp_artistbrowse_create(g_session, art, SP_ARTISTBROWSE_NO_ALBUMS, no_albums_cb, &browse_veronica_maggio); sp_artist_release(art); art = artist_from_uri("spotify:artist:5pKCCKE2ajJHZ9KAiaK11H"); sp_artistbrowse_create(g_session, art, SP_ARTISTBROWSE_NO_ALBUMS, no_albums_cb, &browse_rihanna); sp_artist_release(art); art = artist_from_uri("spotify:artist:43ZHCT0cAZBISjO8DG9PnE"); sp_artistbrowse_create(g_session, art, SP_ARTISTBROWSE_NO_ALBUMS, no_albums_cb, &browse_elvis); sp_artist_release(art); } /***************************************************************** * Artist browse no tracks */ TEST_DECL(browse_no_tracks_elvis, 10); static void SP_CALLCONV no_tracks_cb(sp_artistbrowse *result, void *userdata) { if(!sp_artistbrowse_is_loaded(result)) test_report(userdata, "Result not loaded"); else if(sp_artistbrowse_error(result) != SP_ERROR_OK) test_report(userdata, "%s", sp_error_message(sp_artistbrowse_error(result))); else if(sp_artistbrowse_num_tracks(result)) test_report(userdata, "Expected 0 tracks but got %d", sp_artistbrowse_num_tracks(result)); else test_ok_backend_duration(userdata, sp_artistbrowse_backend_request_duration(result)); sp_artistbrowse_release(result); } static void artistbrowse_no_tracks_test(void) { sp_artist *art; test_start(&browse_no_tracks_elvis); art = artist_from_uri("spotify:artist:43ZHCT0cAZBISjO8DG9PnE"); sp_artistbrowse_create(g_session, art, SP_ARTISTBROWSE_NO_TRACKS, no_tracks_cb, &browse_no_tracks_elvis); sp_artist_release(art); } /***************************************************************** * Toplist browse */ TEST_DECL5(browse_toplist_artist_global, 3, 0, 0, 100); TEST_DECL5(browse_toplist_album_global, 3, 0, 100, 0); TEST_DECL5(browse_toplist_track_global, 3, 100, 0, 0); TEST_DECL5(browse_toplist_artist_user, 3, 0, 0, 20); TEST_DECL5(browse_toplist_album_user, 3, 0, 20, 0); TEST_DECL5(browse_toplist_track_user, 3, 20, 0, 0); TEST_DECL5(browse_toplist_artist_SE, 3, 0, 0, 100); TEST_DECL5(browse_toplist_album_SE, 3, 0, 100, 0); TEST_DECL5(browse_toplist_track_SE, 3, 100, 0, 0); TEST_DECL5(browse_toplist_artist_US, 3, 0, 0, 100); TEST_DECL5(browse_toplist_album_US, 3, 0, 100, 0); TEST_DECL5(browse_toplist_track_US, 3, 100, 0, 0); static void SP_CALLCONV toplist_cb(sp_toplistbrowse *result, void *userdata) { const Test *t = userdata; if(!sp_toplistbrowse_is_loaded(result)) test_report(userdata, "Result not loaded"); else if(sp_toplistbrowse_error(result) != SP_ERROR_OK) test_report(userdata, "%s", sp_error_message(sp_toplistbrowse_error(result))); else if(t->arg1 != sp_toplistbrowse_num_tracks(result)) test_report(userdata, "Expected %d tracks but got %d", t->arg1, sp_toplistbrowse_num_tracks(result)); else if(t->arg2 != sp_toplistbrowse_num_albums(result)) test_report(userdata, "Expected %d albums but got %d", t->arg2, sp_toplistbrowse_num_albums(result)); else if(t->arg3 != sp_toplistbrowse_num_artists(result)) test_report(userdata, "Expected %d artists but got %d", t->arg3, sp_toplistbrowse_num_artists(result)); else test_ok_backend_duration(userdata, sp_toplistbrowse_backend_request_duration(result)); sp_toplistbrowse_release(result); } static void toplistbrowse_test(void) { test_start(&browse_toplist_artist_global); test_start(&browse_toplist_album_global); test_start(&browse_toplist_track_global); test_start(&browse_toplist_artist_user); test_start(&browse_toplist_album_user); test_start(&browse_toplist_track_user); test_start(&browse_toplist_artist_SE); test_start(&browse_toplist_album_SE); test_start(&browse_toplist_track_SE); test_start(&browse_toplist_artist_US); test_start(&browse_toplist_album_US); test_start(&browse_toplist_track_US); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_ARTISTS, SP_TOPLIST_REGION_EVERYWHERE, NULL, toplist_cb, &browse_toplist_artist_global); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_ALBUMS, SP_TOPLIST_REGION_EVERYWHERE, NULL, toplist_cb, &browse_toplist_album_global); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_TRACKS, SP_TOPLIST_REGION_EVERYWHERE, NULL, toplist_cb, &browse_toplist_track_global); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_ARTISTS, SP_TOPLIST_REGION_USER, NULL, toplist_cb, &browse_toplist_artist_user); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_ALBUMS, SP_TOPLIST_REGION_USER, NULL, toplist_cb, &browse_toplist_album_user); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_TRACKS, SP_TOPLIST_REGION_USER, NULL, toplist_cb, &browse_toplist_track_user); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_ARTISTS, SP_TOPLIST_REGION('S', 'E'), NULL, toplist_cb, &browse_toplist_artist_SE); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_ALBUMS, SP_TOPLIST_REGION('S', 'E'), NULL, toplist_cb, &browse_toplist_album_SE); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_TRACKS, SP_TOPLIST_REGION('S', 'E'), NULL, toplist_cb, &browse_toplist_track_SE); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_ARTISTS, SP_TOPLIST_REGION('U', 'S'), NULL, toplist_cb, &browse_toplist_artist_US); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_ALBUMS, SP_TOPLIST_REGION('U', 'S'), NULL, toplist_cb, &browse_toplist_album_US); sp_toplistbrowse_create(g_session, SP_TOPLIST_TYPE_TRACKS, SP_TOPLIST_REGION('U', 'S'), NULL, toplist_cb, &browse_toplist_track_US); } /***************************************************************** * Radio */ #ifdef SP_WITH_RADIO TEST_DECL(radio_artist,3); TEST_DECL(radio_track,3); TEST_DECL(radio_genre,3); static void radio_cb(sp_radio *result, void *userdata) { int num_tracks; sp_track* track; if(!sp_radio_is_loaded(result)) test_report(userdata, "Result not loaded"); else if(sp_radio_error(result) != SP_ERROR_OK) test_report(userdata, "%s", sp_error_message(sp_radio_error(result))); else { num_tracks=sp_radio_num_tracks(result); if (num_tracks>0) { track = sp_radio_track(result, num_tracks - 1); if ( track ) { test_ok(userdata); } else { test_report(userdata, "Can't get last track"); } } else { test_report(userdata, "No tracks returned"); } } sp_radio_release(result); } static void radio_test(void) { sp_link* link; test_start(&radio_artist); link = sp_link_create_from_string("spotify:artist:6tbjWDEIzxoDsBA1FuhfPW"); // Madonna sp_radio_create_from_link(g_session, link, &radio_cb, &radio_artist); sp_link_release( link ); test_start(&radio_track); link = sp_link_create_from_string("spotify:track:1z3ugFmUKoCzGsI6jdY4Ci"); // Like A Prayer sp_radio_create_from_link(g_session, link, &radio_cb, &radio_track); sp_link_release( link ); test_start(&radio_genre); sp_radio_create_from_genre(g_session, SP_RADIO_GENRE_DANCE, &radio_cb, &radio_genre); } #endif /***************************************************************** * Streaming test */ TEST_DECL(playtrack, 25); static sp_track *stream_track; static int stream_track_end; /** * Callback from spotify session. Happen on different thread so we need to signal and wakeup */ void SP_CALLCONV end_of_track(sp_session *s) { stream_track_end = 1; notify_main_thread(g_session); info_report("end of track"); } /** * Callback from spotify session. Just consume all frames */ int SP_CALLCONV music_delivery(sp_session *s, const sp_audioformat *fmt, const void *frames, int num_frames) { return num_frames; } /** * Callback from spotify session. Happen on different thread so we need to signal and wakeup */ void SP_CALLCONV play_token_lost(sp_session *s) { stream_track_end = 2; notify_main_thread(g_session); info_report("Playtoken lost"); } static void playtrack_test(void) { sp_error err; test_start(&playtrack); if((err = sp_session_player_load(g_session, stream_track)) != SP_ERROR_OK) { test_report(&playtrack, "Unable to load track: %s", sp_error_message(err)); return; } info_report("Streaming '%s' by '%s' this will take a while", sp_track_name(stream_track), sp_artist_name(sp_track_artist(stream_track, 0))); sp_session_player_play(g_session, 1); } static int check_streaming_done(void) { if(stream_track_end == 2) test_report(&playtrack, "Playtoken lost"); else if(stream_track_end == 1) test_ok(&playtrack); else return 0; stream_track_end = 0; return 1; } /***************************************************************** * Image loading */ TEST_DECL3(image1, 3, 4560901); TEST_DECL3(image2, 3, 4920563); TEST_DECL3(image3, 3, 4440836); TEST_DECL3(image4, 3, 2202765); TEST_DECL3(image5, 3, 3254083); TEST_DECL3(image6, 3, 2595372); TEST_DECL3(image7, 3, 10028876); TEST_DECL3(image8, 3, 7017370); TEST_DECL3(image9, 3, 3655831); static void SP_CALLCONV image_cb(sp_image *image, void *userdata) { Test *t = userdata; size_t size; const byte *data; int sum; size_t i; data = sp_image_data(image, &size); sum = size; for(i = 0; i < size; i++) sum += data[i]; sp_image_release(image); if(sum != t->arg1) test_report(userdata, "Invalid checksum"); else test_ok(userdata); } static void load_image(Test *t, const char *uri) { sp_link *l; sp_image *img; test_start(t); l = sp_link_create_from_string(uri); img = sp_image_create_from_link(g_session, l); sp_image_add_load_callback(img, image_cb, t); sp_link_release(l); } static void image_test(void) { load_image(&image1, "spotify:image:34062b6f0168af33b9cd80c630a38f5de183f936"); load_image(&image2, "spotify:image:654873a4d6a648f24055a5faec324aa2a80a997d"); load_image(&image3, "spotify:image:5a9b36ac716b1178dc35bd05db96a34eafb77ad4"); load_image(&image4, "spotify:image:564ce92718a792307f40634cb3225f0e91d7965b"); load_image(&image5, "spotify:image:67b169a8a8df439437df7424ccd5dd7b7d88fca8"); load_image(&image6, "spotify:image:b05e1aaf5824c9e4736b5d7b173aec5fdd4e6a4e"); load_image(&image7, "spotify:image:0f44e39b1e7c21701640bb5ad035efde02b6aeeb"); load_image(&image8, "spotify:image:c588570590c7d76670efad294424b991ddde17a0"); load_image(&image9, "spotify:image:53a580c6e3e314838dcbc2ed97ebebb54a25693e"); } /********************************************************************************* * State machine */ #define WAIT_FOR_TEST(t) state = __LINE__; case __LINE__: if(!(t)->done) return #define WAIT_FOR(expr) state = __LINE__; case __LINE__: if(!(expr)) return static void print_resource_usage() { #if __APPLE__ || __linux__ struct rusage r_usage; int res; res = getrusage(RUSAGE_SELF, &r_usage); if (res == 0) { printf("getrusage() returned:\n"); #if __APPLE__ #define TV_USEC_FORMAT "d" #elif __linux__ #define TV_USEC_FORMAT "ld" #endif printf(" User time: %ld.%03" TV_USEC_FORMAT "s\n", r_usage.ru_utime.tv_sec, r_usage.ru_utime.tv_usec / 1000); printf(" System time: %ld.%03" TV_USEC_FORMAT "s\n", r_usage.ru_stime.tv_sec, r_usage.ru_stime.tv_usec / 1000); #undef TV_USEC_FORMAT #if __APPLE__ #define MAXRSS_UNITS "bytes" #elif __linux__ #define MAXRSS_UNITS "kilobytes" #endif printf(" Peak memory usage: %ld " MAXRSS_UNITS " (ru_maxrss)\n", r_usage.ru_maxrss); // integral max resident set size #undef MAXRSS_UNITS } else { perror("getrusage() failed"); } #endif // __APPLE__ || __linux__ } /** * */ void test_process(void) { static int state; switch(state) { case 0: search_test(); WAIT_FOR(!active_tests); albumbrowse_test(); WAIT_FOR(!active_tests); artistbrowse_test(); WAIT_FOR(!active_tests); artistbrowse_no_tracks_test(); WAIT_FOR(!active_tests); image_test(); WAIT_FOR(!active_tests); toplistbrowse_test(); WAIT_FOR(!active_tests); #ifdef SP_WITH_RADIO radio_test(); WAIT_FOR(!active_tests); #endif info_report("Loading %s", "spotify:track:5iIeIeH3LBSMK92cMIXrVD"); stream_track = track_from_uri("spotify:track:5iIeIeH3LBSMK92cMIXrVD"); WAIT_FOR(sp_track_is_loaded(stream_track)); playtrack_test(); WAIT_FOR(check_streaming_done()); WAIT_FOR(!active_tests); sp_track_release(stream_track); state = -1; test_finished(); print_resource_usage(); } } int cmd_test(int argc, char **argv) { extern int g_selftest; g_selftest = 1; test_process(); return -1; } ================================================ FILE: libspotify/examples/spshell/toplist.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include "spshell.h" #include "cmd.h" /** * */ static void print_album(int index, sp_album *album) { printf(" Album %3d: \"%s\" by \"%s\"\n", index, sp_album_name(album), sp_artist_name(sp_album_artist(album))); } /** * */ static void print_artist(int index, sp_artist *artist) { sp_link *l; char url[200]; printf(" Artist %3d: \"%s\"\n", index, sp_artist_name(artist)); l = sp_link_create_from_artist_portrait(artist, SP_IMAGE_SIZE_NORMAL); if(l != NULL) { sp_link_as_string(l, url, sizeof(url)); printf(" Portrait: %s\n", url); sp_link_release(l); } } /** * Callback for libspotify * * @param result The toplist result object that is now done * @param userdata The opaque pointer given to sp_toplistbrowse_create() */ static void SP_CALLCONV got_toplist(sp_toplistbrowse *result, void *userdata) { int i; // We print from all types. Only one of the loops will acually yield anything. for(i = 0; i < sp_toplistbrowse_num_artists(result); i++) print_artist(i + 1, sp_toplistbrowse_artist(result, i)); for(i = 0; i < sp_toplistbrowse_num_albums(result); i++) print_album(i + 1, sp_toplistbrowse_album(result, i)); for(i = 0; i < sp_toplistbrowse_num_tracks(result); i++) { printf("%3d: ", i + 1); print_track(sp_toplistbrowse_track(result, i)); } sp_toplistbrowse_release(result); cmd_done(); } /** * */ static void toplist_usage(void) { fprintf(stderr, "Usage: toplist (tracks | albums | artists) (global | region | user)\n"); } /** * */ int cmd_toplist(int argc, char **argv) { sp_toplisttype type; sp_toplistregion region; if(argc < 3) { toplist_usage(); return -1; } if(!strcasecmp(argv[1], "artists")) type = SP_TOPLIST_TYPE_ARTISTS; else if(!strcasecmp(argv[1], "albums")) type = SP_TOPLIST_TYPE_ALBUMS; else if(!strcasecmp(argv[1], "tracks")) type = SP_TOPLIST_TYPE_TRACKS; else { toplist_usage(); return -1; } if(!strcasecmp(argv[2], "global")) region = SP_TOPLIST_REGION_EVERYWHERE; else if(!strcasecmp(argv[2], "user")) region = SP_TOPLIST_REGION_USER; else if(!strcasecmp(argv[2], "region")) { if(argc != 4 || strlen(argv[3]) != 2) { toplist_usage(); return -1; } region = SP_TOPLIST_REGION(argv[3][0], argv[3][1]); } else { toplist_usage(); return -1; } sp_toplistbrowse_create(g_session, type, region, NULL, got_toplist, NULL); return 0; } ================================================ FILE: libspotify/examples/spshell/win32/spshell.vcproj ================================================ ================================================ FILE: libspotify/examples/spshell/win32/spshell.vcxproj ================================================  Debug Win32 Release Win32 {6C49596C-977E-4BB6-BC94-123AEB5E2724} Win32Proj Application Application <_ProjectFileVersion>10.0.30319.1 Debug\ Debug\ true Release\ Release\ false AllRules.ruleset AllRules.ruleset Disabled $(LIBSPOTIFY)\include;..\..\..\include WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level3 ProgramDatabase StdCall libspotify.lib $(LIBSPOTIFY)\lib;..\..\..\lib true Console false MachineX86 WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) MultiThreadedDLL Level3 ProgramDatabase $(LIBSPOTIFY)\include;..\..\..\include true Console true true false MachineX86 libspotify.lib $(LIBSPOTIFY)\lib;..\..\..\lib ================================================ FILE: libspotify/examples/spshell/win32/spshell.vcxproj.filters ================================================  {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx Header Files Header Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files ================================================ FILE: libspotify/examples/stub/Makefile ================================================ TARGET=posix_stu CFLAGs += -Werror include ../common.mk $(TARGET): main.o $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) $^ -o $@ ifdef DEBUG ifeq ($(shell uname),Darwin) install_name_tool -change @loader_path/../Frameworks/libspotify.framework/libspotify @rpath/libspotify.so $@ endif endif ================================================ FILE: libspotify/examples/stub/main.c ================================================ /** * Copyright (c) 2006-2010 Spotify Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #define USER_AGENT "" #error Please update above variables for your project /* A simple stub that logs in and out */ #include "main.h" #include #include #include #include sp_session *g_session; static int notify_events; static pthread_mutex_t notify_mutex; static pthread_cond_t notify_cond; static void connection_error(sp_session *session, sp_error error) { } static void logged_in(sp_session *session, sp_error error) { //add your testcode here } static void logged_out(sp_session *session) { exit(0); } /** * This callback is called for log messages. * * @sa sp_session_callbacks#log_message */ static void log_message(sp_session *session, const char *data) { fprintf(stderr,"%s",data); } void notify_main_thread(sp_session *session) { pthread_mutex_lock(¬ify_mutex); notify_events = 1; pthread_cond_signal(¬ify_cond); pthread_mutex_unlock(¬ify_mutex); } static sp_session_callbacks callbacks = { &logged_in, &logged_out, NULL, &connection_error, NULL, ¬ify_main_thread, NULL, NULL, &log_message }; int spotify_init(const char *username,const char *password) { sp_session_config config; sp_error error; sp_session *session; /// The application key is specific to each project, and allows Spotify /// to produce statistics on how our service is used. extern const char g_appkey[]; /// The size of the application key. extern const size_t g_appkey_size; // Always do this. It allows libspotify to check for // header/library inconsistencies. config.api_version = SPOTIFY_API_VERSION; // The path of the directory to store the cache. This must be specified. // Please read the documentation on preferred values. config.cache_location = "tmp"; // The path of the directory to store the settings. // This must be specified. // Please read the documentation on preferred values. config.settings_location = "tmp"; // The key of the application. They are generated by Spotify, // and are specific to each application using libspotify. config.application_key = g_appkey; config.application_key_size = g_appkey_size; // This identifies the application using some // free-text string [1, 255] characters. config.user_agent = USER_AGENT; // Register the callbacks. config.callbacks = &callbacks; error = sp_session_create(&config, &session); if (SP_ERROR_OK != error) { fprintf(stderr, "failed to create session: %s\n", sp_error_message(error)); return 2; } // Login using the credentials given on the command line. error = sp_session_login(session, username, password, 0); if (SP_ERROR_OK != error) { fprintf(stderr, "failed to login: %s\n", sp_error_message(error)); return 3; } g_session = session; return 0; } int main(int argc, char **argv) { int next_timeout = 0; if(argc < 3) { fprintf(stderr,"Usage: %s \n",argv[0]); } pthread_mutex_init(¬ify_mutex, NULL); pthread_cond_init(¬ify_cond, NULL); if(spotify_init(argv[1],argv[2]) != 0) { fprintf(stderr,"Spotify failed to initialize\n"); exit(-1); } pthread_mutex_lock(¬ify_mutex); for (;;) { if (next_timeout == 0) { while(!notify_events) pthread_cond_wait(¬ify_cond, ¬ify_mutex); } else { struct timespec ts; #if _POSIX_TIMERS > 0 clock_gettime(CLOCK_REALTIME, &ts); #else struct timeval tv; gettimeofday(&tv, NULL); TIMEVAL_TO_TIMESPEC(&tv, &ts); #endif ts.tv_sec += next_timeout / 1000; ts.tv_nsec += (next_timeout % 1000) * 1000000; while(!notify_events) { if(pthread_cond_timedwait(¬ify_cond, ¬ify_mutex, &ts)) break; } } // Process libspotify events notify_events = 0; pthread_mutex_unlock(¬ify_mutex); do { sp_session_process_events(g_session, &next_timeout); } while (next_timeout == 0); pthread_mutex_lock(¬ify_mutex); } return 0; } ================================================ FILE: libspotify/examples/stub/main.h ================================================ #ifndef __MAIN_H__ #define __MAIN_H__ #include #endif ================================================ FILE: libspotify/include/libspotify/api.h ================================================ /* * Copyright (c) 2006-2012 Spotify Ltd * * The terms of use for this and related files can be read in * the associated LICENSE file, usually stored in share/doc/libspotify/LICENSE. */ /** * @file api.h Public API for libspotify * * @note All input strings are expected to be in UTF-8 * @note All output strings are in UTF-8. * * @note All usernames are valid XMPP nodeprep identifiers: * http://tools.ietf.org/html/rfc3920#appendix-A * If you need to store user data, we strongly advise you * to use the canonical form of the username. */ #ifndef PUBLIC_API_H #define PUBLIC_API_H #ifdef __cplusplus extern "C" { #endif #ifndef SP_CALLCONV #ifdef _WIN32 #define SP_CALLCONV __stdcall #else #define SP_CALLCONV #endif #endif #ifndef SP_LIBEXPORT #ifdef _WIN32 #define SP_LIBEXPORT(x) x __stdcall #else #define SP_LIBEXPORT(x) x #endif #endif /* Includes */ #include #ifdef _WIN32 typedef unsigned __int64 sp_uint64; #else #include typedef uint64_t sp_uint64; #endif /* General types */ #if !defined(__cplusplus) && !defined(__bool_true_false_are_defined) typedef unsigned char bool; #endif typedef unsigned char byte; /** * @defgroup types Spotify types & structs * * @{ */ typedef struct sp_session sp_session; ///< Representation of a session typedef struct sp_track sp_track; ///< A track handle typedef struct sp_album sp_album; ///< An album handle typedef struct sp_artist sp_artist; ///< An artist handle typedef struct sp_artistbrowse sp_artistbrowse; ///< A handle to an artist browse result typedef struct sp_albumbrowse sp_albumbrowse; ///< A handle to an album browse result typedef struct sp_toplistbrowse sp_toplistbrowse; ///< A handle to a toplist browse result typedef struct sp_search sp_search; ///< A handle to a search result typedef struct sp_link sp_link; ///< A handle to the libspotify internal representation of a URI typedef struct sp_image sp_image; ///< A handle to an image typedef struct sp_user sp_user; ///< A handle to a user typedef struct sp_playlist sp_playlist; ///< A playlist handle typedef struct sp_playlistcontainer sp_playlistcontainer; ///< A playlist container (playlist containing other playlists) handle typedef struct sp_inbox sp_inbox; ///< Add to inbox request handle /** @} */ /** * @defgroup error Error Handling * * All functions in libspotify use the same set of error codes. Most of them return * an error code, and let the result of the operation be stored in an out-parameter. * * @{ */ /** * Error codes returned by various functions */ typedef enum sp_error { SP_ERROR_OK = 0, ///< No errors encountered SP_ERROR_BAD_API_VERSION = 1, ///< The library version targeted does not match the one you claim you support SP_ERROR_API_INITIALIZATION_FAILED = 2, ///< Initialization of library failed - are cache locations etc. valid? SP_ERROR_TRACK_NOT_PLAYABLE = 3, ///< The track specified for playing cannot be played SP_ERROR_BAD_APPLICATION_KEY = 5, ///< The application key is invalid SP_ERROR_BAD_USERNAME_OR_PASSWORD = 6, ///< Login failed because of bad username and/or password SP_ERROR_USER_BANNED = 7, ///< The specified username is banned SP_ERROR_UNABLE_TO_CONTACT_SERVER = 8, ///< Cannot connect to the Spotify backend system SP_ERROR_CLIENT_TOO_OLD = 9, ///< Client is too old, library will need to be updated SP_ERROR_OTHER_PERMANENT = 10, ///< Some other error occurred, and it is permanent (e.g. trying to relogin will not help) SP_ERROR_BAD_USER_AGENT = 11, ///< The user agent string is invalid or too long SP_ERROR_MISSING_CALLBACK = 12, ///< No valid callback registered to handle events SP_ERROR_INVALID_INDATA = 13, ///< Input data was either missing or invalid SP_ERROR_INDEX_OUT_OF_RANGE = 14, ///< Index out of range SP_ERROR_USER_NEEDS_PREMIUM = 15, ///< The specified user needs a premium account SP_ERROR_OTHER_TRANSIENT = 16, ///< A transient error occurred. SP_ERROR_IS_LOADING = 17, ///< The resource is currently loading SP_ERROR_NO_STREAM_AVAILABLE = 18, ///< Could not find any suitable stream to play SP_ERROR_PERMISSION_DENIED = 19, ///< Requested operation is not allowed SP_ERROR_INBOX_IS_FULL = 20, ///< Target inbox is full SP_ERROR_NO_CACHE = 21, ///< Cache is not enabled SP_ERROR_NO_SUCH_USER = 22, ///< Requested user does not exist SP_ERROR_NO_CREDENTIALS = 23, ///< No credentials are stored SP_ERROR_NETWORK_DISABLED = 24, ///< Network disabled SP_ERROR_INVALID_DEVICE_ID = 25, ///< Invalid device ID SP_ERROR_CANT_OPEN_TRACE_FILE = 26, ///< Unable to open trace file SP_ERROR_APPLICATION_BANNED = 27, ///< This application is no longer allowed to use the Spotify service SP_ERROR_OFFLINE_TOO_MANY_TRACKS = 31, ///< Reached the device limit for number of tracks to download SP_ERROR_OFFLINE_DISK_CACHE = 32, ///< Disk cache is full so no more tracks can be downloaded to offline mode SP_ERROR_OFFLINE_EXPIRED = 33, ///< Offline key has expired, the user needs to go online again SP_ERROR_OFFLINE_NOT_ALLOWED = 34, ///< This user is not allowed to use offline mode SP_ERROR_OFFLINE_LICENSE_LOST = 35, ///< The license for this device has been lost. Most likely because the user used offline on three other device SP_ERROR_OFFLINE_LICENSE_ERROR = 36, ///< The Spotify license server does not respond correctly SP_ERROR_LASTFM_AUTH_ERROR = 39, ///< A LastFM scrobble authentication error has occurred SP_ERROR_INVALID_ARGUMENT = 40, ///< An invalid argument was specified SP_ERROR_SYSTEM_FAILURE = 41, ///< An operating system error } sp_error; /** * Convert a numeric libspotify error code to a text string. The error message is in * English. This function is useful for logging purposes. * * @param[in] error The error code to lookup */ SP_LIBEXPORT(const char*) sp_error_message(sp_error error); /** @} */ /** * @defgroup session Session handling * * The concept of a session is fundamental for all communication with the Spotify ecosystem - it is the * object responsible for communicating with the Spotify service. You will need to instantiate a * session that then can be used to request artist information, perform searches etc. * * @{ */ /** * Current version of the application interface, that is, the API described by this file. * * This value should be set in the sp_session_config struct passed to sp_session_create(). * * If an (upgraded) library is no longer compatible with this version the error #SP_ERROR_BAD_API_VERSION will be * returned from sp_session_create(). Future versions of the library will provide you with some kind of mechanism * to request an updated version of the library. */ #define SPOTIFY_API_VERSION 12 /** * Describes the current state of the connection */ typedef enum sp_connectionstate { SP_CONNECTION_STATE_LOGGED_OUT = 0, ///< User not yet logged in SP_CONNECTION_STATE_LOGGED_IN = 1, ///< Logged in against a Spotify access point SP_CONNECTION_STATE_DISCONNECTED = 2, ///< Was logged in, but has now been disconnected SP_CONNECTION_STATE_UNDEFINED = 3, ///< The connection state is undefined SP_CONNECTION_STATE_OFFLINE = 4 ///< Logged in in offline mode } sp_connectionstate; /** * Sample type descriptor */ typedef enum sp_sampletype { SP_SAMPLETYPE_INT16_NATIVE_ENDIAN = 0, ///< 16-bit signed integer samples } sp_sampletype; /** * Audio format descriptor */ typedef struct sp_audioformat { sp_sampletype sample_type; ///< Sample type enum, int sample_rate; ///< Audio sample rate, in samples per second. int channels; ///< Number of channels. Currently 1 or 2. } sp_audioformat; /** * Bitrate definitions for music streaming */ typedef enum sp_bitrate { SP_BITRATE_160k = 0, ///< Bitrate 160kbps SP_BITRATE_320k = 1, ///< Bitrate 320kbps SP_BITRATE_96k = 2, ///< Bitrate 96kbps } sp_bitrate; /** * Playlist types */ typedef enum sp_playlist_type { SP_PLAYLIST_TYPE_PLAYLIST = 0, ///< A normal playlist. SP_PLAYLIST_TYPE_START_FOLDER = 1, ///< Marks a folder starting point, SP_PLAYLIST_TYPE_END_FOLDER = 2, ///< and ending point. SP_PLAYLIST_TYPE_PLACEHOLDER = 3, ///< Unknown entry. } sp_playlist_type; /** * Search types */ typedef enum sp_search_type { SP_SEARCH_STANDARD = 0, SP_SEARCH_SUGGEST = 1, } sp_search_type; /** * Playlist offline status */ typedef enum sp_playlist_offline_status { SP_PLAYLIST_OFFLINE_STATUS_NO = 0, ///< Playlist is not offline enabled SP_PLAYLIST_OFFLINE_STATUS_YES = 1, ///< Playlist is synchronized to local storage SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING = 2, ///< This playlist is currently downloading. Only one playlist can be in this state any given time SP_PLAYLIST_OFFLINE_STATUS_WAITING = 3, ///< Playlist is queued for download } sp_playlist_offline_status; /** * Track availability */ typedef enum sp_availability { SP_TRACK_AVAILABILITY_UNAVAILABLE = 0, ///< Track is not available SP_TRACK_AVAILABILITY_AVAILABLE = 1, ///< Track is available and can be played SP_TRACK_AVAILABILITY_NOT_STREAMABLE = 2, ///< Track can not be streamed using this account SP_TRACK_AVAILABILITY_BANNED_BY_ARTIST = 3, ///< Track not available on artist's reqeust } sp_track_availability; /** * Track offline status */ typedef enum sp_track_offline_status { SP_TRACK_OFFLINE_NO = 0, ///< Not marked for offline SP_TRACK_OFFLINE_WAITING = 1, ///< Waiting for download SP_TRACK_OFFLINE_DOWNLOADING = 2, ///< Currently downloading SP_TRACK_OFFLINE_DONE = 3, ///< Downloaded OK and can be played SP_TRACK_OFFLINE_ERROR = 4, ///< Error during download SP_TRACK_OFFLINE_DONE_EXPIRED = 5, ///< Downloaded OK but not playable due to expiery SP_TRACK_OFFLINE_LIMIT_EXCEEDED = 6, ///< Waiting because device have reached max number of allowed tracks SP_TRACK_OFFLINE_DONE_RESYNC = 7, ///< Downloaded OK and available but scheduled for re-download } sp_track_offline_status; /** * Image size */ typedef enum sp_image_size { SP_IMAGE_SIZE_NORMAL = 0, ///< Normal image size SP_IMAGE_SIZE_SMALL = 1, ///< Small image size SP_IMAGE_SIZE_LARGE = 2, ///< Large image size } sp_image_size; /** * Buffer stats used by get_audio_buffer_stats callback */ typedef struct sp_audio_buffer_stats { int samples; ///< Samples in buffer int stutter; ///< Number of stutters (audio dropouts) since last query } sp_audio_buffer_stats; /** * List of subscribers returned by sp_playlist_subscribers() */ typedef struct sp_subscribers { unsigned int count; ///< Number of elements in 'subscribers' char *subscribers[1]; ///< Actual size is 'count'. Array of pointers to canonical usernames } sp_subscribers; /** * Current connection type set using sp_session_set_connection_type() */ typedef enum sp_connection_type { SP_CONNECTION_TYPE_UNKNOWN = 0, ///< Connection type unknown (Default) SP_CONNECTION_TYPE_NONE = 1, ///< No connection SP_CONNECTION_TYPE_MOBILE = 2, ///< Mobile data (EDGE, 3G, etc) SP_CONNECTION_TYPE_MOBILE_ROAMING = 3, ///< Roamed mobile data (EDGE, 3G, etc) SP_CONNECTION_TYPE_WIFI = 4, ///< Wireless connection SP_CONNECTION_TYPE_WIRED = 5, ///< Ethernet cable, etc } sp_connection_type; /** * Connection rules, bitwise OR of flags * * The default is SP_CONNECTION_RULE_NETWORK | SP_CONNECTION_RULE_ALLOW_SYNC */ typedef enum sp_connection_rules { SP_CONNECTION_RULE_NETWORK = 0x1, ///< Allow network traffic. When not set libspotify will force itself into offline mode SP_CONNECTION_RULE_NETWORK_IF_ROAMING = 0x2, ///< Allow network traffic even if roaming SP_CONNECTION_RULE_ALLOW_SYNC_OVER_MOBILE = 0x4, ///< Set to allow syncing of offline content over mobile connections SP_CONNECTION_RULE_ALLOW_SYNC_OVER_WIFI = 0x8, ///< Set to allow syncing of offline content over WiFi } sp_connection_rules; /** * Controls the type of data that will be included in artist browse queries */ typedef enum sp_artistbrowse_type { SP_ARTISTBROWSE_FULL, /**< All information except tophit tracks This mode is deprecated and will removed in a future release */ SP_ARTISTBROWSE_NO_TRACKS, /**< Only albums and data about them, no tracks. In other words, sp_artistbrowse_num_tracks() will return 0 */ SP_ARTISTBROWSE_NO_ALBUMS, /**< Only return data about the artist (artist name, similar artist biography, etc No tracks or album will be abailable. sp_artistbrowse_num_tracks() and sp_artistbrowse_num_albums() will both return 0 */ } sp_artistbrowse_type; typedef enum sp_social_provider { SP_SOCIAL_PROVIDER_SPOTIFY, SP_SOCIAL_PROVIDER_FACEBOOK, SP_SOCIAL_PROVIDER_LASTFM, } sp_social_provider; typedef enum sp_scrobbling_state { SP_SCROBBLING_STATE_USE_GLOBAL_SETTING = 0, SP_SCROBBLING_STATE_LOCAL_ENABLED = 1, SP_SCROBBLING_STATE_LOCAL_DISABLED = 2, SP_SCROBBLING_STATE_GLOBAL_ENABLED = 3, SP_SCROBBLING_STATE_GLOBAL_DISABLED = 4, } sp_scrobbling_state; /** * Offline sync status */ typedef struct sp_offline_sync_status { /** * Queued tracks/bytes is things left to sync in current sync * operation */ int queued_tracks; sp_uint64 queued_bytes; /** * Done tracks/bytes is things marked for sync that existed on * device before current sync operation */ int done_tracks; sp_uint64 done_bytes; /** * Copied tracks/bytes is things that has been copied in * current sync operation */ int copied_tracks; sp_uint64 copied_bytes; /** * Tracks that are marked as synced but will not be copied * (for various reasons) */ int willnotcopy_tracks; /** * A track is counted as error when something goes wrong while * syncing the track */ int error_tracks; /** * Set if sync operation is in progress */ bool syncing; } sp_offline_sync_status; /** * Session callbacks * * Registered when you create a session. * If some callbacks should not be of interest, set them to NULL. */ typedef struct sp_session_callbacks { /** * Called when login has been processed and was successful * * @param[in] session Session * @param[in] error One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_CLIENT_TOO_OLD * SP_ERROR_UNABLE_TO_CONTACT_SERVER * SP_ERROR_BAD_USERNAME_OR_PASSWORD * SP_ERROR_USER_BANNED * SP_ERROR_USER_NEEDS_PREMIUM * SP_ERROR_OTHER_TRANSIENT * SP_ERROR_OTHER_PERMANENT */ void (SP_CALLCONV *logged_in)(sp_session *session, sp_error error); /** * Called when logout has been processed. Either called explicitly * if you initialize a logout operation, or implicitly if there * is a permanent connection error * * @param[in] session Session */ void (SP_CALLCONV *logged_out)(sp_session *session); /** * Called whenever metadata has been updated * * If you have metadata cached outside of libspotify, you should purge * your caches and fetch new versions. * * @param[in] session Session */ void (SP_CALLCONV *metadata_updated)(sp_session *session); /** * Called when there is a connection error, and the library has problems * reconnecting to the Spotify service. Could be called multiple times (as * long as the problem is present) * * * @param[in] session Session * @param[in] error One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_CLIENT_TOO_OLD * SP_ERROR_UNABLE_TO_CONTACT_SERVER * SP_ERROR_BAD_USERNAME_OR_PASSWORD * SP_ERROR_USER_BANNED * SP_ERROR_USER_NEEDS_PREMIUM * SP_ERROR_OTHER_TRANSIENT * SP_ERROR_OTHER_PERMANENT */ void (SP_CALLCONV *connection_error)(sp_session *session, sp_error error); /** * Called when the access point wants to display a message to the user * * In the desktop client, these are shown in a blueish toolbar just below the * search box. * * @param[in] session Session * @param[in] message String in UTF-8 format. */ void (SP_CALLCONV *message_to_user)(sp_session *session, const char *message); /** * Called when processing needs to take place on the main thread. * * You need to call sp_session_process_events() in the main thread to get * libspotify to do more work. Failure to do so may cause request timeouts, * or a lost connection. * * @param[in] session Session * * @note This function is called from an internal session thread - you need to have proper synchronization! */ void (SP_CALLCONV *notify_main_thread)(sp_session *session); /** * Called when there is decompressed audio data available. * * @param[in] session Session * @param[in] format Audio format descriptor sp_audioformat * @param[in] frames Points to raw PCM data as described by \p format * @param[in] num_frames Number of available samples in \p frames. * If this is 0, a discontinuity has occurred (such as after a seek). The application * should flush its audio fifos, etc. * * @return Number of frames consumed. * This value can be used to rate limit the output from the library if your * output buffers are saturated. The library will retry delivery in about 100ms. * * @note This function is called from an internal session thread - you need to have proper synchronization! * * @note This function must never block. If your output buffers are full you must return 0 to signal * that the library should retry delivery in a short while. */ int (SP_CALLCONV *music_delivery)(sp_session *session, const sp_audioformat *format, const void *frames, int num_frames); /** * Music has been paused because an account only allows music * to be played from one location simultaneously. * * @note When this callback is invoked the application should * behave just as if the user pressed the pause * button. The application should also display a message * to the user indicating the playback has been paused * because another application is playing using the same * account. * * @note IT MUST NOT automatically resume playback but must * instead wait for the user to press play. * * @param[in] session Session */ void (SP_CALLCONV *play_token_lost)(sp_session *session); /** * Logging callback. * * @param[in] session Session * @param[in] data Log data */ void (SP_CALLCONV *log_message)(sp_session *session, const char *data); /** * End of track. * Called when the currently played track has reached its end. * * @note This function is invoked from the main thread * * @param[in] session Session */ void (SP_CALLCONV *end_of_track)(sp_session *session); /** * Streaming error. * Called when streaming cannot start or continue. * * @note This function is invoked from the main thread * * @param[in] session Session * @param[in] error One of the following errors, from ::sp_error * SP_ERROR_NO_STREAM_AVAILABLE * SP_ERROR_OTHER_TRANSIENT * SP_ERROR_OTHER_PERMANENT */ void (SP_CALLCONV *streaming_error)(sp_session *session, sp_error error); /** * Called after user info (anything related to sp_user objects) have been updated. * * @param[in] session Session */ void (SP_CALLCONV *userinfo_updated)(sp_session *session); /** * Called when audio playback should start * * @note For this to work correctly the application must also implement get_audio_buffer_stats() * * @note This function is called from an internal session thread - you need to have proper synchronization! * * @note This function must never block. * * @param[in] session Session */ void (SP_CALLCONV *start_playback)(sp_session *session); /** * Called when audio playback should stop * * @note For this to work correctly the application must also implement get_audio_buffer_stats() * * @note This function is called from an internal session thread - you need to have proper synchronization! * * @note This function must never block. * * @param[in] session Session */ void (SP_CALLCONV *stop_playback)(sp_session *session); /** * Called to query application about its audio buffer * * @note This function is called from an internal session thread - you need to have proper synchronization! * * @note This function must never block. * * @param[in] session Session * @param[out] stats Stats struct to be filled by application */ void (SP_CALLCONV *get_audio_buffer_stats)(sp_session *session, sp_audio_buffer_stats *stats); /** * Called when offline synchronization status is updated * * @param[in] session Session */ void (SP_CALLCONV *offline_status_updated)(sp_session *session); /** * Called when offline synchronization status is updated * * @param[in] session Session * @param[in] error Offline error. Will be SP_ERROR_OK if the offline synchronization * error state has cleared */ void (SP_CALLCONV *offline_error)(sp_session *session, sp_error error); /** * Called when storable credentials have been updated, usually called when * we have connected to the AP. * * @param[in] session Session * @param[in] blob Blob is a null-terminated string which contains * an encrypted token that can be stored safely on disk * instead of storing plaintext passwords. */ void (SP_CALLCONV *credentials_blob_updated)(sp_session *session, const char *blob); /** * Called when the connection state has updated - such as when logging in, going offline, etc. * * @param[in] session Session */ void (SP_CALLCONV *connectionstate_updated)(sp_session *session); /** * Called when there is a scrobble error event * * @param[in] session Session * @param[in] error Scrobble error. Currently SP_ERROR_LASTFM_AUTH_ERROR. */ void (SP_CALLCONV *scrobble_error)(sp_session *session, sp_error error); /** * Called when there is a change in the private session mode * * @param[in] session Session * @param[in] isPrivate True if in private session, false otherwhise */ void (SP_CALLCONV *private_session_mode_changed)(sp_session *session, bool is_private); } sp_session_callbacks; /** * Session config */ typedef struct sp_session_config { int api_version; ///< The version of the Spotify API your application is compiled with. Set to #SPOTIFY_API_VERSION const char *cache_location; /**< The location where Spotify will write cache files. * This cache include tracks, cached browse results and coverarts. * Set to empty string ("") to disable cache */ const char *settings_location; /**< The location where Spotify will write setting files and per-user * cache items. This includes playlists, track metadata, etc. * 'settings_location' may be the same path as 'cache_location'. * 'settings_location' folder will not be created (unlike 'cache_location'), * if you don't want to create the folder yourself, you can set 'settings_location' to 'cache_location'. */ const void *application_key; ///< Your application key size_t application_key_size; ///< The size of the application key in bytes const char *user_agent; /**< "User-Agent" for your application - max 255 characters long The User-Agent should be a relevant, customer facing identification of your application */ const sp_session_callbacks *callbacks; ///< Delivery callbacks for session events, or NULL if you are not interested in any callbacks (not recommended!) void *userdata; ///< User supplied data for your application /** * Compress local copy of playlists, reduces disk space usage */ bool compress_playlists; /** * Don't save metadata for local copies of playlists * Reduces disk space usage at the expense of needing * to request metadata from Spotify backend when loading list */ bool dont_save_metadata_for_playlists; /** * Avoid loading playlists into RAM on startup. * See sp_playlist_is_in_ram() for more details. */ bool initially_unload_playlists; /** * Device ID for offline synchronization and logging purposes. The Device Id must be unique to the particular device instance, * i.e. no two units must supply the same Device ID. The Device ID must not change between sessions or power cycles. * Good examples is the device's MAC address or unique serial number. */ const char *device_id; /** * Url to the proxy server that should be used. * The format is protocol://:port (where protocal is http/https/socks4/socks5) */ const char *proxy; /** * Username to authenticate with proxy server */ const char *proxy_username; /** * Password to authenticate with proxy server */ const char *proxy_password; /** * Path to API trace file */ const char *tracefile; } sp_session_config; /** * Initialize a session. The session returned will be initialized, but you will need * to log in before you can perform any other operation * Currently it is not supported to have multiple active sessions, and it's recommended to only call this once per process. * * Here is a snippet from \c spshell.c: * @dontinclude spshell.c * @skip config.api_version * @until } * * @param[in] config The configuration to use for the session * @param[out] sess If successful, a new session - otherwise NULL * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_BAD_API_VERSION * SP_ERROR_BAD_USER_AGENT * SP_ERROR_BAD_APPLICATION_KEY * SP_ERROR_API_INITIALIZATION_FAILED * SP_ERROR_INVALID_DEVICE_ID */ SP_LIBEXPORT(sp_error) sp_session_create(const sp_session_config *config, sp_session **sess); /** * Release the session. This will clean up all data and connections associated with the session * * @param[in] sess Session object returned from sp_session_create() * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_release(sp_session *sess); /** * Logs in the specified username/password combo. This initiates the login in the background. * A callback is called when login is complete * * An application MUST NEVER store the user's password in clear text. * If automatic relogin is required, use sp_session_relogin() * * Here is a snippet from \c spshell.c: * @dontinclude spshell.c * @skip sp_session_login * @until } * * @param[in] session Your session object * @param[in] username The username to log in * @param[in] password The password for the specified username * @param[in] remember_me If set, the username / password will be remembered by libspotify * @param[in] blob If you have received a blob in the #credentials_blob_updated * you can pas this here instead of password * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_login(sp_session *session, const char *username, const char *password, bool remember_me, const char *blob); /** * Log in the remembered user if last user that logged in logged in with remember_me set. * If no credentials are stored, this will return SP_ERROR_NO_CREDENTIALS. * * @param[in] session Your session object * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_NO_CREDENTIALS */ SP_LIBEXPORT(sp_error) sp_session_relogin(sp_session *session); /** * Get username of the user that will be logged in via sp_session_relogin() * * @param[in] session Your session object * @param[out] buffer The buffer to hold the username * @param[in] buffer_size The max size of the buffer that will hold the username. * The resulting string is guaranteed to always be null terminated if * buffer_size > 0 * * @return The number of characters in the username. * If value is greater or equal than \p buffer_size, output was truncated. * If returned value is -1 no credentials are stored in libspotify. */ SP_LIBEXPORT(int) sp_session_remembered_user(sp_session *session, char *buffer, size_t buffer_size); /** * Get a pointer to a string representing the user's login username. * * @param[in] session Your session object * * @return A string representing the login username. */ SP_LIBEXPORT(const char *) sp_session_user_name(sp_session *session); /** * Remove stored credentials in libspotify. If no credentials are currently stored, nothing will happen. * * @param[in] session Your session object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_forget_me(sp_session *session); /** * Fetches the currently logged in user * * @param[in] session Your session object * * @return The logged in user (or NULL if not logged in) */ SP_LIBEXPORT(sp_user *) sp_session_user(sp_session *session); /** * Logs out the currently logged in user * * Always call this before terminating the application and libspotify is currently * logged in. Otherwise, the settings and cache may be lost. * * @param[in] session Your session object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_logout(sp_session *session); /** * Flush the caches * * This will make libspotify write all data that is meant to be stored * on disk to the disk immediately. libspotify does this periodically * by itself and also on logout. So under normal conditions this * should never need to be used. * * @param[in] session Your session object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_flush_caches(sp_session *session); /** * The connection state of the specified session. * * @param[in] session Your session object * * @return The connection state - see the sp_connectionstate enum for possible values */ SP_LIBEXPORT(sp_connectionstate) sp_session_connectionstate(sp_session *session); /** * The userdata associated with the session * * @param[in] session Your session object * * @return The userdata that was passed in on session creation */ SP_LIBEXPORT(void *) sp_session_userdata(sp_session *session); /** * Set maximum cache size. * * @param[in] session Your session object * @param[in] size Maximum cache size in megabytes. * Setting it to 0 (the default) will let libspotify automatically * resize the cache (10% of disk free space) * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_set_cache_size(sp_session *session, size_t size); /** * Make the specified session process any pending events * * @param[in] session Your session object * @param[out] next_timeout Stores the time (in milliseconds) until you should call this function again * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_process_events(sp_session *session, int *next_timeout); /** * Loads the specified track * * After successfully loading the track, you have the option of running * sp_session_player_play() directly, or using sp_session_player_seek() first. * When this call returns, the track will have been loaded, unless an error occurred. * * @param[in] session Your session object * @param[in] track The track to be loaded * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_MISSING_CALLBACK * SP_ERROR_TRACK_NOT_PLAYABLE * */ SP_LIBEXPORT(sp_error) sp_session_player_load(sp_session *session, sp_track *track); /** * Seek to position in the currently loaded track * * @param[in] session Your session object * @param[in] offset Track position, in milliseconds. * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_player_seek(sp_session *session, int offset); /** * Play or pause the currently loaded track * * @param[in] session Your session object * @param[in] play If set to true, playback will occur. If set to false, the playback will be paused. * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_player_play(sp_session *session, bool play); /** * Stops the currently playing track * * This frees some resources held by libspotify to identify the currently * playing track. * * @param[in] session Your session object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_player_unload(sp_session *session); /** * Prefetch a track * * Instruct libspotify to start loading of a track into its cache. * This could be done by an application just before the current track ends. * * @param[in] session Your session object * @param[in] track The track to be prefetched * * @return One of the following errors, from ::sp_error * SP_ERROR_NO_CACHE * SP_ERROR_OK * * @note Prefetching is only possible if a cache is configured * */ SP_LIBEXPORT(sp_error) sp_session_player_prefetch(sp_session *session, sp_track *track); /** * Returns the playlist container for the currently logged in user. * * @param[in] session Your session object * * @return Playlist container object, NULL if not logged in */ SP_LIBEXPORT(sp_playlistcontainer *) sp_session_playlistcontainer(sp_session *session); /** * Returns an inbox playlist for the currently logged in user * * @param[in] session Session object * * @return A playlist or NULL if no user is logged in * @note You need to release the playlist when you are done with it. * @see sp_playlist_release() */ SP_LIBEXPORT(sp_playlist *) sp_session_inbox_create(sp_session *session); /** * Returns the starred list for the current user * * @param[in] session Session object * * @return A playlist or NULL if no user is logged in * @note You need to release the playlist when you are done with it. * @see sp_playlist_release() */ SP_LIBEXPORT(sp_playlist *) sp_session_starred_create(sp_session *session); /** * Returns the starred list for a user * * @param[in] session Session object * @param[in] canonical_username Canonical username * * @return A playlist or NULL if no user is logged in * @note You need to release the playlist when you are done with it. * @see sp_playlist_release() */ SP_LIBEXPORT(sp_playlist *) sp_session_starred_for_user_create(sp_session *session, const char *canonical_username); /** * Return the published container for a given @a canonical_username, * or the currently logged in user if @a canonical_username is NULL. * * When done with the list you should call sp_playlistconatiner_release() to * decrese the reference you own by having created it. * * @param[in] session Your session object. * @param[in] canonical_username The canonical username, or NULL. * * @return Playlist container object, NULL if not logged in. */ SP_LIBEXPORT(sp_playlistcontainer *) sp_session_publishedcontainer_for_user_create(sp_session *session, const char *canonical_username); /** * Set preferred bitrate for music streaming * * @param[in] session Session object * @param[in] bitrate Preferred bitrate, see ::sp_bitrate for possible values * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INVALID_ARGUMENT */ SP_LIBEXPORT(sp_error) sp_session_preferred_bitrate(sp_session *session, sp_bitrate bitrate); /** * Set preferred bitrate for offline sync * * @param[in] session Session object * @param[in] bitrate Preferred bitrate, see ::sp_bitrate for possible values * @param[in] allow_resync Set to true if libspotify should resynchronize already synchronized tracks. Usually you should set this to false. * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INVALID_ARGUMENT */ SP_LIBEXPORT(sp_error) sp_session_preferred_offline_bitrate(sp_session *session, sp_bitrate bitrate, bool allow_resync); /** * Return status of volume normalization * * @param[in] session Session object * * @return true iff volume normalization is enabled * */ SP_LIBEXPORT(bool) sp_session_get_volume_normalization(sp_session *session); /** * Set volume normalization * * @param[in] session Session object * @param[in] on True iff volume normalization should be enabled * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_set_volume_normalization(sp_session *session, bool on); /** * Set if private session is enabled. This disables sharing what the user is listening to * to services such as Spotify Social, Facebook and LastFM. The private session will * last for a time, and then libspotify will revert to the normal state. The private * session is prolonged by user activity. * * @param[in] session Session object * @param[in] enabled True iff private session should be enabled * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_set_private_session(sp_session *session, bool enabled); /** * Return True if private session is enabled * * @param[in] session Session object * * @return True if private session is enabled */ SP_LIBEXPORT(bool) sp_session_is_private_session(sp_session *session); /** * Set if scrobbling is enabled. This api allows setting local overrides of the global scrobbling settings. * Changing the global settings are currently not supported. * * @param[in] session Session object * @param[in] provider The scrobbling provider referred to * @param[in] state The state to set the provider to * * @return error code * * @see sp_social_provider * @see sp_scrobbling_state */ SP_LIBEXPORT(sp_error) sp_session_set_scrobbling(sp_session *session, sp_social_provider provider, sp_scrobbling_state state); /** * Return the scrobbling state. This makes it possible to find out if scrobbling is locally overrided or * if the global setting is used. * * @param[in] session Session object * @param[in] provider The scrobbling provider referred to * @param[out] state The output variable receiving the sp_scrobbling_state state * * @return error code */ SP_LIBEXPORT(sp_error) sp_session_is_scrobbling(sp_session *session, sp_social_provider provider, sp_scrobbling_state* state); /** * Return True if scrobbling settings should be shown to the user. Currently this setting is relevant * only to the facebook provider. * The returned value may be false if the user is not connected to facebook, * or if the user has opted out from facebook social graph. * * @param[in] session Session object * @param[in] provider The scrobbling provider referred to * @param[out] out True iff scrobbling is possible * * @return error code */ SP_LIBEXPORT(sp_error) sp_session_is_scrobbling_possible(sp_session *session, sp_social_provider provider, bool* out); /** * Set the user's credentials with a social provider. * Currently this is only relevant for LastFm * Call sp_session_set_scrobbling to force an authentication attempt * with the LastFm server. If authentication fails a scrobble_error callback will be * sent. * * @param[in] session Session object * @param[in] provider The scrobbling provider referred to * @param[in] username The user name * @param[in] password The password * * @return error code * @see sp_session_set_scrobbling * @see sp_session_callbacks#scrobble_error * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_set_social_credentials(sp_session *session, sp_social_provider provider, const char* username, const char* password); /** * Set to true if the connection is currently routed over a roamed connectivity * * @param[in] session Session object * @param[in] type Connection type * * @note Used in conjunction with sp_session_set_connection_rules() to control * how libspotify should behave in respect to network activity and offline * synchronization. * @see sp_connection_type * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_set_connection_type(sp_session *session, sp_connection_type type); /** * Set rules for how libspotify connects to Spotify servers and synchronizes offline content * * @param[in] session Session object * @param[in] rules Connection rules * * @note Used in conjunction with sp_session_set_connection_type() to control * how libspotify should behave in respect to network activity and offline * synchronization. * @see sp_connection_rules * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_session_set_connection_rules(sp_session *session, sp_connection_rules rules); /** * Get total number of tracks that needs download before everything * from all playlists that is marked for offline is fully synchronized * * @param[in] session Session object * * @return Number of tracks */ SP_LIBEXPORT(int) sp_offline_tracks_to_sync(sp_session *session); /** * Return number of playlisys that is marked for offline synchronization * * @param[in] session Session object * * @return Number of playlists */ SP_LIBEXPORT(int) sp_offline_num_playlists(sp_session *session); /** * Return offline synchronization status. When the internal status is * updated the offline_status_updated() callback will be invoked. * * @param[in] session Session object * @param[out] status Status object that will be filled with info * * @return false if no synching is in progress (in which case the contents * of status is undefined) * */ SP_LIBEXPORT(bool) sp_offline_sync_get_status(sp_session *session, sp_offline_sync_status *status); /** * Return remaining time (in seconds) until the offline key store expires * and the user is required to relogin * * @param[in] session Session object * @return Seconds until expiration * */ SP_LIBEXPORT(int) sp_offline_time_left(sp_session *session); /** * Get currently logged in users country * updated the offline_status_updated() callback will be invoked. * * @param[in] session Session object * * @return Country encoded in an integer 'SE' = 'S' << 8 | 'E' */ SP_LIBEXPORT(int) sp_session_user_country(sp_session *session); /** @} */ /** * @defgroup link Links (Spotify URIs) * * These functions handle links to Spotify entities in a way that allows you to * not care about the textual representation of the link. * @{ */ /** * Link types */ typedef enum { SP_LINKTYPE_INVALID = 0, ///< Link type not valid - default until the library has parsed the link, or when parsing failed SP_LINKTYPE_TRACK = 1, ///< Link type is track SP_LINKTYPE_ALBUM = 2, ///< Link type is album SP_LINKTYPE_ARTIST = 3, ///< Link type is artist SP_LINKTYPE_SEARCH = 4, ///< Link type is search SP_LINKTYPE_PLAYLIST = 5, ///< Link type is playlist SP_LINKTYPE_PROFILE = 6, ///< Link type is profile SP_LINKTYPE_STARRED = 7, ///< Link type is starred SP_LINKTYPE_LOCALTRACK = 8, ///< Link type is a local file SP_LINKTYPE_IMAGE = 9, ///< Link type is an image } sp_linktype; /** * Create a Spotify link given a string * * @param[in] link A string representation of a Spotify link * * @return A link representation of the given string representation. * If the link could not be parsed, this function returns NULL. * * @note You need to release the link when you are done with it. * @see sp_link_type() * @see sp_link_release() */ SP_LIBEXPORT(sp_link *) sp_link_create_from_string(const char *link); /** * Generates a link object from a track * * @param[in] track A track object * @param[in] offset Offset in track in ms. * * @return A link representing the track * * @note You need to release the link when you are done with it. * @see sp_link_release() */ SP_LIBEXPORT(sp_link *) sp_link_create_from_track(sp_track *track, int offset); /** * Create a link object from an album * * @param[in] album An album object * * @return A link representing the album * * @note You need to release the link when you are done with it. * @see sp_link_release() */ SP_LIBEXPORT(sp_link *) sp_link_create_from_album(sp_album *album); /** * Create an image link object from an album * * @param[in] album An album object * @param[in] size The desired size of the image * * @return A link representing the album cover. Type is set to SP_LINKTYPE_IMAGE * * @note You need to release the link when you are done with it. * @see sp_link_release() */ SP_LIBEXPORT(sp_link *) sp_link_create_from_album_cover(sp_album *album, sp_image_size size); /** * Creates a link object from an artist * * @param[in] artist An artist object * * @return A link object representing the artist * * @note You need to release the link when you are done with it. * @see sp_link_release() */ SP_LIBEXPORT(sp_link *) sp_link_create_from_artist(sp_artist *artist); /** * Creates a link object pointing to an artist portrait * * @param[in] artist Artist browse object * @param[in] size The desired size of the image * * @return A link object representing an image * * @note You need to release the link when you are done with it. * @see sp_link_release() * @see sp_artistbrowse_num_portraits() */ SP_LIBEXPORT(sp_link *) sp_link_create_from_artist_portrait(sp_artist *artist, sp_image_size size); /** * Creates a link object from an artist portrait * * @param[in] arb Artist browse object * @param[in] index The index of the portrait. Should be in the interval [0, sp_artistbrowse_num_portraits() - 1] * * @return A link object representing an image * * @note You need to release the link when you are done with it. * @see sp_link_release() * @see sp_artistbrowse_num_portraits() * * @note The difference from sp_link_create_from_artist_portrait() is * that the artist browse object may contain multiple portraits. * */ SP_LIBEXPORT(sp_link *) sp_link_create_from_artistbrowse_portrait(sp_artistbrowse *arb, int index); /** * Generate a link object representing the current search * * @param[in] search Search object * * @return A link representing the search * * @note You need to release the link when you are done with it. * @see sp_link_release() */ SP_LIBEXPORT(sp_link *) sp_link_create_from_search(sp_search *search); /** * Create a link object representing the given playlist * * @param[in] playlist Playlist object * * @return A link representing the playlist * * @note You need to release the link when you are done with it. * @see sp_link_release() * * @note Due to reasons in the playlist backend design and the Spotify URI * scheme you need to wait for the playlist to be loaded before you can * successfully construct an URI. If sp_link_create_from_playlist() returns * NULL, try again after teh playlist_state_changed callback has fired. */ SP_LIBEXPORT(sp_link *) sp_link_create_from_playlist(sp_playlist *playlist); /** * Create a link object representing the given playlist * * @param[in] user User object * * @return A link representing the profile. * * @note You need to release the link when you are done with it. * @see sp_link_release() */ SP_LIBEXPORT(sp_link *) sp_link_create_from_user(sp_user *user); /** * Create a link object representing the given image * * @param[in] image Image object * * @return A link representing the image. * * @note You need to release the link when you are done with it. * @see sp_link_release() */ SP_LIBEXPORT(sp_link *) sp_link_create_from_image(sp_image *image); /** * Create a string representation of the given Spotify link * * @param[in] link The Spotify link whose string representation you are interested in * @param[out] buffer The buffer to hold the string representation of link * @param[in] buffer_size The max size of the buffer that will hold the string representation * The resulting string is guaranteed to always be null terminated if * buffer_size > 0 * * @return The number of characters in the string representation of the link. If this * value is greater or equal than \p buffer_size, output was truncated. */ SP_LIBEXPORT(int) sp_link_as_string(sp_link *link, char *buffer, int buffer_size); /** * The link type of the specified link * * @param[in] link The Spotify link whose type you are interested in * * @return The link type of the specified link - see the sp_linktype enum for possible values */ SP_LIBEXPORT(sp_linktype) sp_link_type(sp_link *link); /** * The track representation for the given link * * @param[in] link The Spotify link whose track you are interested in * * @return The track representation of the given track link * If the link is not of track type then NULL is returned. */ SP_LIBEXPORT(sp_track *) sp_link_as_track(sp_link *link); /** * The track and offset into track representation for the given link * * @param[in] link The Spotify link whose track you are interested in * @param[out] offset Pointer to offset into track (in milliseconds). If the link * does not contain an offset this will be set to 0. * * @return The track representation of the given track link * If the link is not of track type then NULL is returned. */ SP_LIBEXPORT(sp_track *) sp_link_as_track_and_offset(sp_link *link, int *offset); /** * The album representation for the given link * * @param[in] link The Spotify link whose album you are interested in * * @return The album representation of the given album link * If the link is not of album type then NULL is returned */ SP_LIBEXPORT(sp_album *) sp_link_as_album(sp_link *link); /** * The artist representation for the given link * * @param[in] link The Spotify link whose artist you are interested in * * @return The artist representation of the given link * If the link is not of artist type then NULL is returned */ SP_LIBEXPORT(sp_artist *) sp_link_as_artist(sp_link *link); /** * The user representation for the given link * * @param[in] link The Spotify link whose user you are interested in * * @return The user representation of the given link * If the link is not of user type then NULL is returned */ SP_LIBEXPORT(sp_user *) sp_link_as_user(sp_link *link); /** * Increase the reference count of a link * * @param[in] link The link object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_link_add_ref(sp_link *link); /** * Decrease the reference count of a link * * @param[in] link The link object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_link_release(sp_link *link); /** @} */ /** * @defgroup track Track subsystem * @{ */ /** * Return whether or not the track metadata is loaded. * * @param[in] track The track * * @return True if track is loaded * * @note This is equivalent to checking if sp_track_error() not returns SP_ERROR_IS_LOADING. */ SP_LIBEXPORT(bool) sp_track_is_loaded(sp_track *track); /** * Return an error code associated with a track. For example if it could not load * * @param[in] track The track * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_IS_LOADING * SP_ERROR_OTHER_PERMANENT */ SP_LIBEXPORT(sp_error) sp_track_error(sp_track *track); /** * Return offline status for a track. sp_session_callbacks::metadata_updated() will be invoked when * offline status of a track changes * * @param[in] track The track * * @return Stats as described by ::sp_track_offline_status * */ SP_LIBEXPORT(sp_track_offline_status) sp_track_offline_get_status(sp_track *track); /** * Return availability for a track * * @param[in] session Session * @param[in] track The track * * @return Availability status, see ::sp_track_availability * * @note The track must be loaded or this function will always SP_TRACK_AVAILABILITY_UNAVAILABLE * @see sp_track_is_loaded() */ SP_LIBEXPORT(sp_track_availability) sp_track_get_availability(sp_session *session, sp_track *track); /** * Return true if the track is a local file. * * @param[in] session Session * @param[in] track The track * * @return True if track is a local file. * * @note The track must be loaded or this function will always return false. * @see sp_track_is_loaded() */ SP_LIBEXPORT(bool) sp_track_is_local(sp_session *session, sp_track *track); /** * Return true if the track is autolinked to another track. * * @param[in] session Session * @param[in] track The track * * @return True if track is autolinked. * * @note The track must be loaded or this function will always return false. * @see sp_track_is_loaded() */ SP_LIBEXPORT(bool) sp_track_is_autolinked(sp_session *session, sp_track *track); /** * Return the actual track that will be played if the given track is played * * @param[in] session Session * @param[in] track The track * * @return A track * */ SP_LIBEXPORT(sp_track *) sp_track_get_playable(sp_session *session, sp_track *track); /** * Return true if the track is a placeholder. Placeholder tracks are used * to store other objects than tracks in the playlist. Currently this is * used in the inbox to store artists, albums and playlists. * * Use sp_link_create_from_track() to get a link object that points * to the real object this "track" points to. * * @param[in] track The track * * @return True if track is a placeholder * * @note Contrary to most functions the track does not have to be * loaded for this function to return correct value */ SP_LIBEXPORT(bool) sp_track_is_placeholder(sp_track *track); /** * Return true if the track is starred by the currently logged in user. * * @param[in] session Session * @param[in] track The track * * @return True if track is starred. * * @note The track must be loaded or this function will always return false. * @see sp_track_is_loaded() */ SP_LIBEXPORT(bool) sp_track_is_starred(sp_session *session, sp_track *track); /** * Star/Unstar the specified track * * @param[in] session Session * @param[in] tracks Array of pointer to tracks. * @param[in] num_tracks Length of \p tracks array * @param[in] star Starred status of the track * * @note This will fail silently if playlists are disabled. * @see sp_set_playlists_enabled() */ SP_LIBEXPORT(sp_error) sp_track_set_starred(sp_session *session, sp_track *const*tracks, int num_tracks, bool star); /** * The number of artists performing on the specified track * * @param[in] track The track whose number of participating artists you are interested in * * @return The number of artists performing on the specified track. * If no metadata is available for the track yet, this function returns 0. */ SP_LIBEXPORT(int) sp_track_num_artists(sp_track *track); /** * The artist matching the specified index performing on the current track. * * @param[in] track The track whose participating artist you are interested in * @param[in] index The index for the participating artist. Should be in the interval [0, sp_track_num_artists() - 1] * * @return The participating artist, or NULL if invalid index */ SP_LIBEXPORT(sp_artist *) sp_track_artist(sp_track *track, int index); /** * The album of the specified track * * @param[in] track A track object * * @return The album of the given track. You need to increase the refcount * if you want to keep the pointer around. * If no metadata is available for the track yet, this function returns 0. */ SP_LIBEXPORT(sp_album *) sp_track_album(sp_track *track); /** * The string representation of the specified track's name * * @param[in] track A track object * * @return The string representation of the specified track's name. * Returned string is valid as long as the album object stays allocated * and no longer than the next call to sp_session_process_events() * If no metadata is available for the track yet, this function returns empty string. */ SP_LIBEXPORT(const char *) sp_track_name(sp_track *track); /** * The duration, in milliseconds, of the specified track * * @param[in] track A track object * * @return The duration of the specified track, in milliseconds * If no metadata is available for the track yet, this function returns 0. */ SP_LIBEXPORT(int) sp_track_duration(sp_track *track); /** * Returns popularity for track * * @param[in] track A track object * * @return Popularity in range 0 to 100, 0 if undefined. * If no metadata is available for the track yet, this function returns 0. */ SP_LIBEXPORT(int) sp_track_popularity(sp_track *track); /** * Returns the disc number for a track * * @param[in] track A track object * * @return Disc index. Possible values are [1, total number of discs on album] * This function returns valid data only for tracks appearing in a browse * artist or browse album result (otherwise returns 0). */ SP_LIBEXPORT(int) sp_track_disc(sp_track *track); /** * Returns the position of a track on its disc * * @param[in] track A track object * * @return Track position, starts at 1 (relative the corresponding disc) * This function returns valid data only for tracks appearing in a browse * artist or browse album result (otherwise returns 0). */ SP_LIBEXPORT(int) sp_track_index(sp_track *track); /** * Returns the newly created local track * * @param[in] artist Name of the artist * @param[in] title Song title * @param[in] album Name of the album, or an empty string if not available * @param[in] length Length in MS, or -1 if not available. * * @return A track. */ SP_LIBEXPORT(sp_track *) sp_localtrack_create(const char *artist, const char *title, const char *album, int length); /** * Increase the reference count of a track * * @param[in] track The track object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_track_add_ref(sp_track *track); /** * Decrease the reference count of a track * * @param[in] track The track object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_track_release(sp_track *track); /** @} */ /** * @defgroup album Album subsystem * @{ */ /** * Album types */ typedef enum { SP_ALBUMTYPE_ALBUM = 0, ///< Normal album SP_ALBUMTYPE_SINGLE = 1, ///< Single SP_ALBUMTYPE_COMPILATION = 2, ///< Compilation SP_ALBUMTYPE_UNKNOWN = 3, ///< Unknown type } sp_albumtype; /** * Check if the album object is populated with data * * @param[in] album Album object * @return True if metadata is present, false if not */ SP_LIBEXPORT(bool) sp_album_is_loaded(sp_album *album); /** * Return true if the album is available in the current region. * * @param[in] album The album * * @return True if album is available for playback, otherwise false. * * @note The album must be loaded or this function will always return false. * @see sp_album_is_loaded() */ SP_LIBEXPORT(bool) sp_album_is_available(sp_album *album); /** * Get the artist associated with the given album * * @param[in] album Album object * @return A reference to the artist. NULL if the metadata has not been loaded yet */ SP_LIBEXPORT(sp_artist *) sp_album_artist(sp_album *album); /** * Return image ID representing the album's coverart. * * @param[in] album Album object * @param[in] size The desired size of the image * * @return ID byte sequence that can be passed to sp_image_create() * If the album has no image or the metadata for the album is not * loaded yet, this function returns NULL. * * @see sp_image_create */ SP_LIBEXPORT(const byte *) sp_album_cover(sp_album *album, sp_image_size size); /** * Return name of album * * @param[in] album Album object * * @return Name of album. * Returned string is valid as long as the album object stays allocated * and no longer than the next call to sp_session_process_events() */ SP_LIBEXPORT(const char *) sp_album_name(sp_album *album); /** * Return release year of specified album * * @param[in] album Album object * * @return Release year */ SP_LIBEXPORT(int) sp_album_year(sp_album *album); /** * Return type of specified album * * @param[in] album Album object * * @return sp_albumtype */ SP_LIBEXPORT(sp_albumtype) sp_album_type(sp_album *album); /** * Increase the reference count of an album * * @param[in] album The album object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_album_add_ref(sp_album *album); /** * Decrease the reference count of an album * * @param[in] album The album object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_album_release(sp_album *album); /** @} */ /** * @defgroup artist Artist subsystem * @{ */ /** * Return name of artist * * @param[in] artist Artist object * * @return Name of artist. * Returned string is valid as long as the artist object stays allocated * and no longer than the next call to sp_session_process_events() */ SP_LIBEXPORT(const char *) sp_artist_name(sp_artist *artist); /** * Check if the artist object is populated with data * * @param[in] artist An artist object * * @return True if metadata is present, false if not * */ SP_LIBEXPORT(bool) sp_artist_is_loaded(sp_artist *artist); /** * Return portrait for artist * * @param[in] artist The artist object * @param[in] size The desired size of the image * * @return ID byte sequence that can be passed to sp_image_create() * If the artist has no image or the metadata for the album is not * loaded yet, this function returns NULL. * */ SP_LIBEXPORT(const byte *) sp_artist_portrait(sp_artist *artist, sp_image_size size); /** * Increase the reference count of a artist * * @param[in] artist The artist object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_artist_add_ref(sp_artist *artist); /** * Decrease the reference count of a artist * * @param[in] artist The artist object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_artist_release(sp_artist *artist); /** @} */ /** * @defgroup albumbrowse Album browsing * * Browsing adds additional information to what an ::sp_album holds. It retrieves * copyrights, reviews and tracks of the album. * * @{ */ /** * The type of a callback used in sp_albumbrowse_create() * * When the callback is called, the metadata of all tracks belonging to it will have * been loaded, so sp_track_is_loaded() will return non-zero. The ::sp_artist of the * album will also have been fully loaded. * * @param[in] result The same pointer returned by sp_albumbrowse_create() * @param[in] userdata The opaque pointer given to sp_albumbrowse_create() */ typedef void SP_CALLCONV albumbrowse_complete_cb(sp_albumbrowse *result, void *userdata); /** * Initiate a request for browsing an album * * The user is responsible for freeing the returned album browse using sp_albumbrowse_release(). This can be done in the callback. * * @param[in] session Session object * @param[in] album Album to be browsed. The album metadata does not have to be loaded * @param[in] callback Callback to be invoked when browsing has been completed. Pass NULL if you are not interested in this event. * @param[in] userdata Userdata passed to callback. * * @return Album browse object * * @see ::albumbrowse_complete_cb */ SP_LIBEXPORT(sp_albumbrowse *) sp_albumbrowse_create(sp_session *session, sp_album *album, albumbrowse_complete_cb *callback, void *userdata); /** * Check if an album browse request is completed * * @param[in] alb Album browse object * * @return True if browsing is completed, false if not */ SP_LIBEXPORT(bool) sp_albumbrowse_is_loaded(sp_albumbrowse *alb); /** * Check if browsing returned an error code. * * @param[in] alb Album browse object * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_IS_LOADING * SP_ERROR_OTHER_PERMANENT * SP_ERROR_OTHER_TRANSIENT */ SP_LIBEXPORT(sp_error) sp_albumbrowse_error(sp_albumbrowse *alb); /** * Given an album browse object, return the pointer to its album object * * @param[in] alb Album browse object * * @return Album object */ SP_LIBEXPORT(sp_album *) sp_albumbrowse_album(sp_albumbrowse *alb); /** * Given an album browse object, return the pointer to its artist object * * @param[in] alb Album browse object * * @return Artist object */ SP_LIBEXPORT(sp_artist *) sp_albumbrowse_artist(sp_albumbrowse *alb); /** * Given an album browse object, return number of copyright strings * * @param[in] alb Album browse object * * @return Number of copyright strings available, 0 if unknown */ SP_LIBEXPORT(int) sp_albumbrowse_num_copyrights(sp_albumbrowse *alb); /** * Given an album browse object, return one of its copyright strings * * @param[in] alb Album browse object * @param[in] index The index for the copyright string. Should be in the interval [0, sp_albumbrowse_num_copyrights() - 1] * * @return Copyright string in UTF-8 format, or NULL if the index is invalid. * Returned string is valid as long as the album object stays allocated * and no longer than the next call to sp_session_process_events() */ SP_LIBEXPORT(const char *) sp_albumbrowse_copyright(sp_albumbrowse *alb, int index); /** * Given an album browse object, return number of tracks * * @param[in] alb Album browse object * * @return Number of tracks on album */ SP_LIBEXPORT(int) sp_albumbrowse_num_tracks(sp_albumbrowse *alb); /** * Given an album browse object, return a pointer to one of its tracks * * @param[in] alb Album browse object * @param[in] index The index for the track. Should be in the interval [0, sp_albumbrowse_num_tracks() - 1] * * @return A track. * * @see track */ SP_LIBEXPORT(sp_track *) sp_albumbrowse_track(sp_albumbrowse *alb, int index); /** * Given an album browse object, return its review * * @param[in] alb Album browse object * * @return Review string in UTF-8 format. * Returned string is valid as long as the album object stays allocated * and no longer than the next call to sp_session_process_events() */ SP_LIBEXPORT(const char *) sp_albumbrowse_review(sp_albumbrowse *alb); /** * Return the time (in ms) that was spent waiting for the Spotify backend to serve the request * * @param[in] alb Album browse object * * @return -1 if the request was served from the local cache * If the result is not yet loaded the return value is undefined */ SP_LIBEXPORT(int) sp_albumbrowse_backend_request_duration(sp_albumbrowse *alb); /** * Increase the reference count of an album browse result * * @param[in] alb The album browse result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_albumbrowse_add_ref(sp_albumbrowse *alb); /** * Decrease the reference count of an album browse result * * @param[in] alb The album browse result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_albumbrowse_release(sp_albumbrowse *alb); /** @} */ /** * @defgroup artistbrowse Artist browsing * * Artist browsing initiates the fetching of information for a certain artist. * * @note There is currently no built-in functionality available for getting the albums belonging * to an artist. For now, just iterate over all tracks and note the album to build a list of all albums. * This feature will be added in a future version of the library. * * @{ */ /** * The type of a callback used in sp_artistbrowse_create() * * When the callback is called, the metadata of all tracks belonging to it will have * been loaded, so sp_track_is_loaded() will return non-zero. The same goes for the * similar artist data. * * @param[in] result The same pointer returned by sp_artistbrowse_create() * @param[in] userdata The opaque pointer given to sp_artistbrowse_create() */ typedef void SP_CALLCONV artistbrowse_complete_cb(sp_artistbrowse *result, void *userdata); /** * Initiate a request for browsing an artist * * The user is responsible for freeing the returned artist browse using sp_artistbrowse_release(). This can be done in the callback. * * @param[in] session Session object * @param[in] artist Artist to be browsed. The artist metadata does not have to be loaded * @param[in] type Type of data requested, see the sp_artistbrowse_type enum for details * @param[in] callback Callback to be invoked when browsing has been completed. Pass NULL if you are not interested in this event. * @param[in] userdata Userdata passed to callback. * * @return Artist browse object * * @see ::artistbrowse_complete_cb */ SP_LIBEXPORT(sp_artistbrowse *) sp_artistbrowse_create(sp_session *session, sp_artist *artist, sp_artistbrowse_type type, artistbrowse_complete_cb *callback, void *userdata); /** * Check if an artist browse request is completed * * @param[in] arb Artist browse object * * @return True if browsing is completed, false if not */ SP_LIBEXPORT(bool) sp_artistbrowse_is_loaded(sp_artistbrowse *arb); /** * Check if browsing returned an error code. * * @param[in] arb Artist browse object * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_IS_LOADING * SP_ERROR_OTHER_PERMANENT * SP_ERROR_OTHER_TRANSIENT */ SP_LIBEXPORT(sp_error) sp_artistbrowse_error(sp_artistbrowse *arb); /** * Given an artist browse object, return a pointer to its artist object * * @param[in] arb Artist browse object * * @return Artist object */ SP_LIBEXPORT(sp_artist *) sp_artistbrowse_artist(sp_artistbrowse *arb); /** * Given an artist browse object, return number of portraits available * * @param[in] arb Artist browse object * * @return Number of portraits for given artist */ SP_LIBEXPORT(int) sp_artistbrowse_num_portraits(sp_artistbrowse *arb); /** * Return image ID representing a portrait of the artist * * @param[in] arb Artist object * @param[in] index The index of the portrait. Should be in the interval [0, sp_artistbrowse_num_portraits() - 1] * * @return ID byte sequence that can be passed to sp_image_create() * * @see sp_image_create */ SP_LIBEXPORT(const byte *) sp_artistbrowse_portrait(sp_artistbrowse *arb, int index); /** * Given an artist browse object, return number of tracks * * @param[in] arb Artist browse object * * @return Number of tracks for given artist */ SP_LIBEXPORT(int) sp_artistbrowse_num_tracks(sp_artistbrowse *arb); /** * Given an artist browse object, return one of its tracks * * @param[in] arb Album browse object * @param[in] index The index for the track. Should be in the interval [0, sp_artistbrowse_num_tracks() - 1] * * @return A track object, or NULL if the index is out of range. * * @see track */ SP_LIBEXPORT(sp_track *) sp_artistbrowse_track(sp_artistbrowse *arb, int index); /** * Given an artist browse object, return number of tophit tracks * This is a set of tracks for the artist with highest popularity * * @param[in] arb Artist browse object * * @return Number of tophit tracks for given artist */ SP_LIBEXPORT(int) sp_artistbrowse_num_tophit_tracks(sp_artistbrowse *arb); /** * Given an artist browse object, return one of its tophit tracks * This is a set of tracks for the artist with highest popularity * * @param[in] arb Album browse object * @param[in] index The index for the track. Should be in the interval [0, sp_artistbrowse_num_tophit_tracks() - 1] * * @return A track object, or NULL if the index is out of range. * * @see track */ SP_LIBEXPORT(sp_track *) sp_artistbrowse_tophit_track(sp_artistbrowse *arb, int index); /** * Given an artist browse object, return number of albums * * @param[in] arb Artist browse object * * @return Number of albums for given artist */ SP_LIBEXPORT(int) sp_artistbrowse_num_albums(sp_artistbrowse *arb); /** * Given an artist browse object, return one of its albums * * @param[in] arb Album browse object * @param[in] index The index for the album. Should be in the interval [0, sp_artistbrowse_num_albums() - 1] * * @return A album object, or NULL if the index is out of range. * * @see album */ SP_LIBEXPORT(sp_album *) sp_artistbrowse_album(sp_artistbrowse *arb, int index); /** * Given an artist browse object, return number of similar artists * * @param[in] arb Artist browse object * * @return Number of similar artists for given artist */ SP_LIBEXPORT(int) sp_artistbrowse_num_similar_artists(sp_artistbrowse *arb); /** * Given an artist browse object, return a similar artist by index * * @param[in] arb Album browse object * @param[in] index The index for the artist. Should be in the interval [0, sp_artistbrowse_num_similar_artists() - 1] * * @return A pointer to an artist object. * * @see artist */ SP_LIBEXPORT(sp_artist *) sp_artistbrowse_similar_artist(sp_artistbrowse *arb, int index); /** * Given an artist browse object, return the artists biography * * @note This function must be called from the same thread that did sp_session_create() * @param[in] arb Artist browse object * * @return Biography string in UTF-8 format. * Returned string is valid as long as the album object stays allocated * and no longer than the next call to sp_session_process_events() */ SP_LIBEXPORT(const char *) sp_artistbrowse_biography(sp_artistbrowse *arb); /** * Return the time (in ms) that was spent waiting for the Spotify backend to serve the request * * @param[in] arb Artist browse object * * @return -1 if the request was served from the local cache * If the result is not yet loaded the return value is undefined */ SP_LIBEXPORT(int) sp_artistbrowse_backend_request_duration(sp_artistbrowse *arb); /** * Increase the reference count of an artist browse result * * @param[in] arb The artist browse result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_artistbrowse_add_ref(sp_artistbrowse *arb); /** * Decrease the reference count of an artist browse result * * @param[in] arb The artist browse result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_artistbrowse_release(sp_artistbrowse *arb); /** @} */ /** * @defgroup image Image handling * @{ */ /** * Image format */ typedef enum { SP_IMAGE_FORMAT_UNKNOWN = -1, ///< Unknown image format SP_IMAGE_FORMAT_JPEG = 0, ///< JPEG image } sp_imageformat; /** * The type of a callback used to notify the application that an image * is done loading. */ typedef void SP_CALLCONV image_loaded_cb(sp_image *image, void *userdata); /** * Create an image object * * @param[in] session Session * @param[in] image_id Spotify image ID * * @return Pointer to an image object. To free the object, use * sp_image_release() * * @see sp_album_cover * @see sp_artistbrowse_portrait */ SP_LIBEXPORT(sp_image *) sp_image_create(sp_session *session, const byte image_id[20]); /** * Create an image object from a link * * @param[in] session Session * @param[in] l Spotify link object. This must be of SP_LINKTYPE_IMAGE type * * @return Pointer to an image object. To free the object, use * sp_image_release() * * @see sp_image_create */ SP_LIBEXPORT(sp_image *) sp_image_create_from_link(sp_session *session, sp_link *l); /** * Add a callback that will be invoked when the image is loaded * * If an image is loaded, and loading fails, the image will behave like an * empty image. * * @param[in] image Image object * @param[in] callback Callback that will be called when image has been * fetched. * @param[in] userdata Opaque pointer passed to \p callback * */ SP_LIBEXPORT(sp_error) sp_image_add_load_callback(sp_image *image, image_loaded_cb *callback, void *userdata); /** * Remove an image load callback previously added with sp_image_add_load_callback() * * @param[in] image Image object * @param[in] callback Callback that will not be called when image has been * fetched. * @param[in] userdata Opaque pointer passed to \p callback * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_image_remove_load_callback(sp_image *image, image_loaded_cb *callback, void *userdata); /** * Check if an image is loaded. Before the image is loaded, the rest of the * methods will behave as if the image is empty. * * @param[in] image Image object * * @return True if image is loaded, false otherwise */ SP_LIBEXPORT(bool) sp_image_is_loaded(sp_image *image); /** * Check if image retrieval returned an error code. * * @param[in] image Image object * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_IS_LOADING * SP_ERROR_OTHER_PERMANENT * SP_ERROR_OTHER_TRANSIENT */ SP_LIBEXPORT(sp_error) sp_image_error(sp_image *image); /** * Get image format * * @param[in] image Image object * * @return Image format as described by sp_imageformat */ SP_LIBEXPORT(sp_imageformat) sp_image_format(sp_image *image); /** * Get image data * * @param[in] image Image object * @param[out] data_size Size of raw image data * * @return Pointer to raw image data */ SP_LIBEXPORT(const void *) sp_image_data(sp_image *image, size_t *data_size); /** * Get image ID * * @param[in] image Image object * * @return Image ID */ SP_LIBEXPORT(const byte *) sp_image_image_id(sp_image *image); /** * Increase the reference count of an image * * @param[in] image The image object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_image_add_ref(sp_image *image); /** * Decrease the reference count of an image * * @param[in] image The image object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_image_release(sp_image *image); /** @} */ /** * @defgroup search Search subsystem * @{ */ /** * The type of a callback used in sp_search_create() * * When this callback is called, the sp_track_is_loaded(), sp_album_is_loaded(), * and sp_artist_is_loaded() functions will return non-zero for the objects * contained in the search result. * * @param[in] result The same pointer returned by sp_search_create() * @param[in] userdata The opaque pointer given to sp_search_create() */ typedef void SP_CALLCONV search_complete_cb(sp_search *result, void *userdata); /** * Create a search object from the given query * * @param[in] session Session * @param[in] query Query search string, e.g. 'The Rolling Stones' or 'album:"The Black Album"' * @param[in] track_offset The offset among the tracks of the result * @param[in] track_count The number of tracks to ask for * @param[in] album_offset The offset among the albums of the result * @param[in] album_count The number of albums to ask for * @param[in] artist_offset The offset among the artists of the result * @param[in] artist_count The number of artists to ask for * @param[in] playlist_offset The offset among the playlists of the result * @param[in] playlist_count The number of playlists to ask for * @param[in] search_type Type of search, can be used for suggest searches * @param[in] callback Callback that will be called once the search operation is complete. Pass NULL if you are not interested in this event. * @param[in] userdata Opaque pointer passed to \p callback * * @return Pointer to a search object. To free the object, use sp_search_release() */ SP_LIBEXPORT(sp_search *) sp_search_create(sp_session *session, const char *query, int track_offset, int track_count, int album_offset, int album_count, int artist_offset, int artist_count, int playlist_offset, int playlist_count, sp_search_type search_type, search_complete_cb *callback, void *userdata); /** * Get load status for the specified search. Before it is loaded, it will behave as an empty search result. * * @param[in] search Search object * * @return True if search is loaded, otherwise false */ SP_LIBEXPORT(bool) sp_search_is_loaded(sp_search *search); /** * Check if search returned an error code. * * @param[in] search Search object * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_IS_LOADING * SP_ERROR_OTHER_PERMANENT * SP_ERROR_OTHER_TRANSIENT */ SP_LIBEXPORT(sp_error) sp_search_error(sp_search *search); /** * Get the number of tracks for the specified search * * @param[in] search Search object * * @return The number of tracks for the specified search */ SP_LIBEXPORT(int) sp_search_num_tracks(sp_search *search); /** * Return the track at the given index in the given search object * * @param[in] search Search object * @param[in] index Index of the wanted track. Should be in the interval [0, sp_search_num_tracks() - 1] * * @return The track at the given index in the given search object */ SP_LIBEXPORT(sp_track *) sp_search_track(sp_search *search, int index); /** * Get the number of albums for the specified search * * @param[in] search Search object * * @return The number of albums for the specified search */ SP_LIBEXPORT(int) sp_search_num_albums(sp_search *search); /** * Return the album at the given index in the given search object * * @param[in] search Search object * @param[in] index Index of the wanted album. Should be in the interval [0, sp_search_num_albums() - 1] * * @return The album at the given index in the given search object */ SP_LIBEXPORT(sp_album *) sp_search_album(sp_search *search, int index); /** * Get the number of playlists for the specified search * * @param[in] search Search object * * @return The number of playlists for the specified search */ SP_LIBEXPORT(int) sp_search_num_playlists(sp_search *search); /** * Load the playlist at the given index in the given search object * * @param[in] search Search object * @param[in] index Index of the wanted playlist. Should be in the interval [0, sp_search_num_playlists() - 1] * * @return A playlist object. This reference is owned by the caller and should be released with sp_playlist_release() */ SP_LIBEXPORT(sp_playlist *) sp_search_playlist(sp_search *search, int index); /** * Return the playlist at the given index in the given search object * * @param[in] search Search object * @param[in] index Index of the wanted playlist. Should be in the interval [0, sp_search_num_playlists() - 1] * * @return The playlist name at the given index in the given search object */ SP_LIBEXPORT(const char *) sp_search_playlist_name(sp_search *search, int index); /** * Return the uri of a playlist at the given index in the given search object * * @param[in] search Search object * @param[in] index Index of the wanted playlist. Should be in the interval [0, sp_search_num_playlists() - 1] * * @return The playlist uri at the given index in the given search object */ SP_LIBEXPORT(const char *) sp_search_playlist_uri(sp_search *search, int index); /** * Return the image_uri of a playlist at the given index in the given search object * * @param[in] search Search object * @param[in] index Index of the wanted playlist. Should be in the interval [0, sp_search_num_playlists() - 1] * * @return The playlist image_uri at the given index in the given search object */ SP_LIBEXPORT(const char *) sp_search_playlist_image_uri(sp_search *search, int index); /** * Get the number of artists for the specified search * * @param[in] search Search object * * @return The number of artists for the specified search */ SP_LIBEXPORT(int) sp_search_num_artists(sp_search *search); /** * Return the artist at the given index in the given search object * * @param[in] search Search object * @param[in] index Index of the wanted artist. Should be in the interval [0, sp_search_num_artists() - 1] * * @return The artist at the given index in the given search object */ SP_LIBEXPORT(sp_artist *) sp_search_artist(sp_search *search, int index); /** * Return the search query for the given search object * * @param[in] search Search object * * @return The search query for the given search object */ SP_LIBEXPORT(const char *) sp_search_query(sp_search *search); /** * Return the "Did you mean" query for the given search object * * @param[in] search Search object * * @return The "Did you mean" query for the given search object, or the empty string if no such info is available */ SP_LIBEXPORT(const char *) sp_search_did_you_mean(sp_search *search); /** * Return the total number of tracks for the search query - regardless of the interval requested at creation. * If this value is larger than the interval specified at creation of the search object, more search results are available. * To fetch these, create a new search object with a new interval. * * @param[in] search Search object * * @return The total number of tracks matching the original query */ SP_LIBEXPORT(int) sp_search_total_tracks(sp_search *search); /** * Return the total number of albums for the search query - regardless of the interval requested at creation. * If this value is larger than the interval specified at creation of the search object, more search results are available. * To fetch these, create a new search object with a new interval. * * @param[in] search Search object * * @return The total number of albums matching the original query */ SP_LIBEXPORT(int) sp_search_total_albums(sp_search *search); /** * Return the total number of artists for the search query - regardless of the interval requested at creation. * If this value is larger than the interval specified at creation of the search object, more search results are available. * To fetch these, create a new search object with a new interval. * * @param[in] search Search object * * @return The total number of artists matching the original query */ SP_LIBEXPORT(int) sp_search_total_artists(sp_search *search); /** * Return the total number of playlists for the search query - regardless of the interval requested at creation. * If this value is larger than the interval specified at creation of the search object, more search results are available. * To fetch these, create a new search object with a new interval. * * @param[in] search Search object * * @return The total number of playlists matching the original query */ SP_LIBEXPORT(int) sp_search_total_playlists(sp_search *search); /** * Increase the reference count of a search result * * @param[in] search The search result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_search_add_ref(sp_search *search); /** * Decrease the reference count of a search result * * @param[in] search The search result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_search_release(sp_search *search); /** @} */ /** * @defgroup playlist Playlist subsystem * * The playlist subsystem handles playlists and playlist containers (list of playlists). * * The playlist container functions are always valid, but your playlists are not * guaranteed to be loaded until the sp_session_callbacks#logged_in callback has * been issued. * * @{ */ /** * Playlist callbacks * * Used to get notifications when playlists are updated. * If some callbacks should not be of interest, set them to NULL. */ typedef struct sp_playlist_callbacks { /** * Called when one or more tracks have been added to a playlist * * @param[in] pl Playlist object * @param[in] tracks Array of pointers to track objects * @param[in] num_tracks Number of entries in \p tracks * @param[in] position Position in the playlist for the first track. * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *tracks_added)(sp_playlist *pl, sp_track * const *tracks, int num_tracks, int position, void *userdata); /** * Called when one or more tracks have been removed from a playlist * * @param[in] pl Playlist object * @param[in] tracks Array of positions representing the tracks that were removed * @param[in] num_tracks Number of entries in \p tracks * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *tracks_removed)(sp_playlist *pl, const int *tracks, int num_tracks, void *userdata); /** * Called when one or more tracks have been moved within a playlist * * @param[in] pl Playlist object * @param[in] tracks Array of positions representing the tracks that were moved * @param[in] num_tracks Number of entries in \p tracks * @param[in] position New position in the playlist for the first track. * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *tracks_moved)(sp_playlist *pl, const int *tracks, int num_tracks, int new_position, void *userdata); /** * Called when a playlist has been renamed. sp_playlist_name() can be used to find out the new name * * @param[in] pl Playlist object * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *playlist_renamed)(sp_playlist *pl, void *userdata); /** * Called when state changed for a playlist. * * There are three states that trigger this callback: * - Collaboration for this playlist has been turned on or off * - The playlist started having pending changes, or all pending changes have now been committed * - The playlist started loading, or finished loading * * @param[in] pl Playlist object * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() * @sa sp_playlist_is_collaborative * @sa sp_playlist_has_pending_changes * @sa sp_playlist_is_loaded */ void (SP_CALLCONV *playlist_state_changed)(sp_playlist *pl, void *userdata); /** * Called when a playlist is updating or is done updating * * This is called before and after a series of changes are applied to the * playlist. It allows e.g. the user interface to defer updating until the * entire operation is complete. * * @param[in] pl Playlist object * @param[in] done True iff the update is completed * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *playlist_update_in_progress)(sp_playlist *pl, bool done, void *userdata); /** * Called when metadata for one or more tracks in a playlist has been updated. * * @param[in] pl Playlist object * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *playlist_metadata_updated)(sp_playlist *pl, void *userdata); /** * Called when create time and/or creator for a playlist entry changes * * @param[in] pl Playlist object * @param[in] position Position in playlist * @param[in] user User object * @param[in] time When entry was created, seconds since the unix epoch. * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *track_created_changed)(sp_playlist *pl, int position, sp_user *user, int when, void *userdata); /** * Called when seen attribute for a playlist entry changes. * * @param[in] pl Playlist object * @param[in] position Position in playlist * @param[in] seen Set if entry it marked as seen * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *track_seen_changed)(sp_playlist *pl, int position, bool seen, void *userdata); /** * Called when playlist description has changed * * @param[in] pl Playlist object * @param[in] desc New description * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *description_changed)(sp_playlist *pl, const char *desc, void *userdata); /** * Called when playlist image has changed * * @param[in] pl Playlist object * @param[in] image New image * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *image_changed)(sp_playlist *pl, const byte *image, void *userdata); /** * Called when message attribute for a playlist entry changes. * * @param[in] pl Playlist object * @param[in] position Position in playlist * @param[in] message UTF-8 encoded message * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *track_message_changed)(sp_playlist *pl, int position, const char *message, void *userdata); /** * Called when playlist subscribers changes (count or list of names) * * @param[in] pl Playlist object * @param[in] userdata Userdata passed to sp_playlist_add_callbacks() */ void (SP_CALLCONV *subscribers_changed)(sp_playlist *pl, void *userdata); } sp_playlist_callbacks; /** * Get load status for the specified playlist. If it's false, you have to wait until * playlist_state_changed happens, and check again if is_loaded has changed * * @param[in] playlist Playlist object * * @return True if playlist is loaded, otherwise false */ SP_LIBEXPORT(bool) sp_playlist_is_loaded(sp_playlist *playlist); /** * Register interest in the given playlist * * Here is a snippet from \c jukebox.c: * @dontinclude jukebox.c * @skipline sp_playlist_add_callbacks * * @param[in] playlist Playlist object * @param[in] callbacks Callbacks, see #sp_playlist_callbacks * @param[in] userdata Userdata to be passed to callbacks * @sa sp_playlist_remove_callbacks * */ SP_LIBEXPORT(sp_error) sp_playlist_add_callbacks(sp_playlist *playlist, sp_playlist_callbacks *callbacks, void *userdata); /** * Unregister interest in the given playlist * * The combination of (\p callbacks, \p userdata) is used to find the entry to be removed * * Here is a snippet from \c jukebox.c: * @dontinclude jukebox.c * @skipline sp_playlist_remove_callbacks * * @param[in] playlist Playlist object * @param[in] callbacks Callbacks, see #sp_playlist_callbacks * @param[in] userdata Userdata to be passed to callbacks * @sa sp_playlist_add_callbacks * @return One of the following errors, from ::sp_error * SP_ERROR_OK * */ SP_LIBEXPORT(sp_error) sp_playlist_remove_callbacks(sp_playlist *playlist, sp_playlist_callbacks *callbacks, void *userdata); /** * Return number of tracks in the given playlist * * @param[in] playlist Playlist object * * @return The number of tracks in the playlist */ SP_LIBEXPORT(int) sp_playlist_num_tracks(sp_playlist *playlist); /** * Return the track at the given index in a playlist * * @param[in] playlist Playlist object * @param[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1] * * @return The track at the given index */ SP_LIBEXPORT(sp_track *) sp_playlist_track(sp_playlist *playlist, int index); /** * Return when the given index was added to the playlist * * @param[in] playlist Playlist object * @param[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1] * * @return Time, Seconds since unix epoch. */ SP_LIBEXPORT(int) sp_playlist_track_create_time(sp_playlist *playlist, int index); /** * Return user that added the given index in the playlist * * @param[in] playlist Playlist object * @param[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1] * * @return User object */ SP_LIBEXPORT(sp_user *) sp_playlist_track_creator(sp_playlist *playlist, int index); /** * Return if a playlist entry is marked as seen or not * * @param[in] playlist Playlist object * @param[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1] * * @return Seen state */ SP_LIBEXPORT(bool) sp_playlist_track_seen(sp_playlist *playlist, int index); /** * Set seen status of a playlist entry * * @param[in] playlist Playlist object * @param[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1] * @param[in] seen Seen status to be set * * @return error One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INDEX_OUT_OF_RANGE */ SP_LIBEXPORT(sp_error) sp_playlist_track_set_seen(sp_playlist *playlist, int index, bool seen); /** * Return a message attached to a playlist item. Typically used on inbox. * * @param[in] playlist Playlist object * @param[in] index Index into playlist container. Should be in the interval [0, sp_playlist_num_tracks() - 1] * * @return UTF-8 encoded message, or NULL if no message is present */ SP_LIBEXPORT(const char *) sp_playlist_track_message(sp_playlist *playlist, int index); /** * Return name of given playlist * * @param[in] playlist Playlist object * * @return The name of the given playlist */ SP_LIBEXPORT(const char *) sp_playlist_name(sp_playlist *playlist); /** * Rename the given playlist * The name must not consist of only spaces and it must be shorter than 256 characters. * * @param[in] playlist Playlist object * @param[in] new_name New name for playlist * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INVALID_INDATA * SP_ERROR_PERMISSION_DENIED */ SP_LIBEXPORT(sp_error) sp_playlist_rename(sp_playlist *playlist, const char *new_name); /** * Return a pointer to the user for the given playlist * * @param[in] playlist Playlist object * * @return User object */ SP_LIBEXPORT(sp_user *) sp_playlist_owner(sp_playlist *playlist); /** * Return collaborative status for a playlist. * * A playlist in collaborative state can be modifed by all users, not only the user owning the list * * @param[in] playlist Playlist object * * @return true if playlist is collaborative, otherwise false */ SP_LIBEXPORT(bool) sp_playlist_is_collaborative(sp_playlist *playlist); /** * Set collaborative status for a playlist. * * A playlist in collaborative state can be modified by all users, not only the user owning the list * * @param[in] playlist Playlist object * @param[in] collaborative True or false * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlist_set_collaborative(sp_playlist *playlist, bool collaborative); /** * Set autolinking state for a playlist. * * If a playlist is autolinked, unplayable tracks will be made playable * by linking them to other Spotify tracks, where possible. * * @param[in] playlist Playlist object * @param[in] link True or false * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlist_set_autolink_tracks(sp_playlist *playlist, bool link); /** * Get description for a playlist * * @param[in] playlist Playlist object * * @return Playlist description or NULL if unset * */ SP_LIBEXPORT(const char *) sp_playlist_get_description(sp_playlist *playlist); /** * Get description for a playlist * * @param[in] playlist Playlist object * @param[out] image 20 byte image id * @return TRUE if playlist has an image, FALSE if not * */ SP_LIBEXPORT(bool) sp_playlist_get_image(sp_playlist *playlist, byte image[20]); /** * Check if a playlist has pending changes * * Pending changes are local changes that have not yet been acknowledged by the server. * * @param[in] playlist Playlist object * * @return A flag representing if there are pending changes or not */ SP_LIBEXPORT(bool) sp_playlist_has_pending_changes(sp_playlist *playlist); /** * Add tracks to a playlist * * @param[in] playlist Playlist object * @param[in] tracks Array of pointer to tracks. * @param[in] num_tracks Length of \p tracks array * @param[in] position Start position in playlist where to insert the tracks * @param[in] session Your session object * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INVALID_INDATA - position is > current playlist length * SP_ERROR_PERMISSION_DENIED */ SP_LIBEXPORT(sp_error) sp_playlist_add_tracks(sp_playlist *playlist, sp_track *const*tracks, int num_tracks, int position, sp_session *session); /** * Remove tracks from a playlist * * @param[in] playlist Playlist object * @param[in] tracks Array of pointer to track indices. * A certain track index should be present at most once, e.g. [0, 1, 2] is valid indata, * whereas [0, 1, 1] is invalid. * @param[in] num_tracks Length of \p tracks array * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_PERMISSION_DENIED */ SP_LIBEXPORT(sp_error) sp_playlist_remove_tracks(sp_playlist *playlist, const int *tracks, int num_tracks); /** * Move tracks in playlist * * @param[in] playlist Playlist object * @param[in] tracks Array of pointer to track indices to be moved. * A certain track index should be present at most once, e.g. [0, 1, 2] is valid indata, * whereas [0, 1, 1] is invalid. * @param[in] num_tracks Length of \p tracks array * @param[in] new_position New position for tracks * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INVALID_INDATA - position is > current playlist length * SP_ERROR_PERMISSION_DENIED */ SP_LIBEXPORT(sp_error) sp_playlist_reorder_tracks(sp_playlist *playlist, const int *tracks, int num_tracks, int new_position); /** * Return number of subscribers for a given playlist * * @param[in] playlist Playlist object * * @return Number of subscribers * */ SP_LIBEXPORT(unsigned int) sp_playlist_num_subscribers(sp_playlist *playlist); /** * Return subscribers for a playlist * * @param[in] playlist Playlist object * * @return sp_subscribers struct with array of canonical usernames. * This object should be free'd using sp_playlist_subscribers_free() * * @note The count returned for this function may be less than those * returned by sp_playlist_num_subscribers(). Spotify does not * track each user subscribed to a playlist for playlist with * many (>500) subscribers. */ SP_LIBEXPORT(sp_subscribers *) sp_playlist_subscribers(sp_playlist *playlist); /** * Free object returned from sp_playlist_subscribers() * * @param[in] subscribers Subscribers object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlist_subscribers_free(sp_subscribers *subscribers); /** * Ask library to update the subscription count for a playlist * * When the subscription info has been fetched from the Spotify backend * the playlist subscribers_changed() callback will be invoked. * In that callback use sp_playlist_num_subscribers() and/or * sp_playlist_subscribers() to get information about the subscribers. * You can call those two functions anytime you want but the information * might not be up to date in such cases * * @param[in] session Session object * @param[in] playlist Playlist object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlist_update_subscribers(sp_session *session, sp_playlist *playlist); /** * Return whether a playlist is loaded in RAM (as opposed to only * stored on disk) * * @param[in] session Session object * @param[in] playlist Playlist object * * @return True iff playlist is in RAM, False otherwise * * @note When a playlist is no longer in RAM it will appear empty. * However, libspotify will retain information about the * list metadata (owner, title, picture, etc) in RAM. * There is one caveat tough: If libspotify has never seen the * playlist before this metadata will also be unset. * In order for libspotify to get the metadata the playlist * needs to be loaded at least once. * In order words, if libspotify starts with an empty playlist * cache and the application has set 'initially_unload_playlists' * config parameter to True all playlists will be empty. * It will not be possible to generate URI's to the playlists * nor extract playlist title until the application calls * sp_playlist_set_in_ram(..., true). So an application * that needs to stay within a low memory profile would need to * cycle thru all new playlists in order to extract metadata. * * The easiest way to detect this case is when * sp_playlist_is_in_ram() returns false and * sp_link_create_from_playlist() returns NULL */ SP_LIBEXPORT(bool) sp_playlist_is_in_ram(sp_session *session, sp_playlist *playlist); /** * Return whether a playlist is loaded in RAM (as opposed to only * stored on disk) * * @param[in] session Session object * @param[in] playlist Playlist object * @param[in] in_ram Controls whether or not to keep the list in RAM * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlist_set_in_ram(sp_session *session, sp_playlist *playlist, bool in_ram); /** * Load an already existing playlist without adding it to a playlistcontainer. * * @param[in] session Session object * @param[in] link Link object referring to a playlist * * @return A playlist. The reference is owned by the caller and should be released with sp_playlist_release() * */ SP_LIBEXPORT(sp_playlist *) sp_playlist_create(sp_session *session, sp_link *link); /** * Mark a playlist to be synchronized for offline playback. * The playlist must be in the users playlistcontainer * * @param[in] session Session object * @param[in] playlist Playlist object * @param[in] offline True iff playlist should be offline, false otherwise * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlist_set_offline_mode(sp_session *session, sp_playlist *playlist, bool offline); /** * Get offline status for a playlist * * @param[in] session Session object * @param[in] playlist Playlist object * * @return sp_playlist_offline_status * * @see When in SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING mode the * sp_playlist_get_offline_download_completed() method can be used to query * progress of the download */ SP_LIBEXPORT(sp_playlist_offline_status) sp_playlist_get_offline_status(sp_session *session, sp_playlist *playlist); /** * Get download progress for an offline playlist * * @param[in] session Session object * @param[in] playlist Playlist object * * @return Value from 0 - 100 that indicates amount of playlist that is downloaded * or 0 if the playlist is not in the SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING mode. * * @see sp_playlist_offline_status() */ SP_LIBEXPORT(int) sp_playlist_get_offline_download_completed(sp_session *session, sp_playlist *playlist); /** * Increase the reference count of a playlist * * @param[in] playlist The playlist object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlist_add_ref(sp_playlist *playlist); /** * Decrease the reference count of a playlist * * @param[in] playlist The playlist object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlist_release(sp_playlist *playlist); /** * Playlist container callbacks. * If some callbacks should not be of interest, set them to NULL. * * @see sp_playlistcontainer_add_callbacks * @see sp_playlistcontainer_remove_callbacks */ typedef struct sp_playlistcontainer_callbacks { /** * Called when a new playlist has been added to the playlist container. * * @param[in] pc Playlist container * @param[in] playlist Playlist object. * @param[in] position Position in list * @param[in] userdata Userdata as set in sp_playlistcontainer_add_callbacks() */ void (SP_CALLCONV *playlist_added)(sp_playlistcontainer *pc, sp_playlist *playlist, int position, void *userdata); /** * Called when a new playlist has been removed from playlist container * * @param[in] pc Playlist container * @param[in] playlist Playlist object. * @param[in] position Position in list * @param[in] userdata Userdata as set in sp_playlistcontainer_add_callbacks() */ void (SP_CALLCONV *playlist_removed)(sp_playlistcontainer *pc, sp_playlist *playlist, int position, void *userdata); /** * Called when a playlist has been moved in the playlist container * * @param[in] pc Playlist container * @param[in] playlist Playlist object. * @param[in] position Previous position in playlist container list * @param[in] new_position New position in playlist container list * @param[in] userdata Userdata as set in sp_playlistcontainer_add_callbacks() */ void (SP_CALLCONV *playlist_moved)(sp_playlistcontainer *pc, sp_playlist *playlist, int position, int new_position, void *userdata); /** * Called when the playlist container is loaded * * @param[in] pc Playlist container * @param[in] userdata Userdata as set in sp_playlistcontainer_add_callbacks() */ void (SP_CALLCONV *container_loaded)(sp_playlistcontainer *pc, void *userdata); } sp_playlistcontainer_callbacks; /** * Register interest in changes to a playlist container * * @param[in] pc Playlist container * @param[in] callbacks Callbacks, see sp_playlistcontainer_callbacks * @param[in] userdata Opaque value passed to callbacks. * * @note Every sp_playlistcontainer_add_callbacks() needs to be paired with a corresponding * sp_playlistcontainer_remove_callbacks() that is invoked before releasing the * last reference you own for the container. In other words, you must make sure * to have removed all the callbacks before the container gets destroyed. * * @sa sp_session_playlistcontainer() * @sa sp_playlistcontainer_remove_callbacks * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlistcontainer_add_callbacks(sp_playlistcontainer *pc, sp_playlistcontainer_callbacks *callbacks, void *userdata); /** * Unregister interest in changes to a playlist container * * @param[in] pc Playlist container * @param[in] callbacks Callbacks, see sp_playlistcontainer_callbacks * @param[in] userdata Opaque value passed to callbacks. * * @sa sp_session_playlistcontainer() * @sa sp_playlistcontainer_add_callbacks * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlistcontainer_remove_callbacks(sp_playlistcontainer *pc, sp_playlistcontainer_callbacks *callbacks, void *userdata); /** * Return the number of playlists in the given playlist container * * @param[in] pc Playlist container * * @return Number of playlists, -1 if undefined * * @sa sp_session_playlistcontainer() */ SP_LIBEXPORT(int) sp_playlistcontainer_num_playlists(sp_playlistcontainer *pc); /** * Return true if the playlistcontainer is fully loaded * * @param[in] pc Playlist container * * @return True if container is loaded * * @note The container_loaded callback will be invoked when this flips to true */ SP_LIBEXPORT(bool) sp_playlistcontainer_is_loaded(sp_playlistcontainer *pc); /** * Return a pointer to the playlist at a specific index * * @param[in] pc Playlist container * @param[in] index Index in playlist container. Should be in the interval [0, sp_playlistcontainer_num_playlists() - 1] * * @return The playlist object * * @sa sp_session_playlistcontainer() */ SP_LIBEXPORT(sp_playlist *) sp_playlistcontainer_playlist(sp_playlistcontainer *pc, int index); /** * Return the type of the playlist at a @a index * * @param[in] pc Playlist container * @param[in] index Index in playlist container. Should be in the interval [0, sp_playlistcontainer_num_playlists() - 1] * * @return Type of the playlist, @see sp_playlist_type * * @sa sp_session_playlistcontainer() */ SP_LIBEXPORT(sp_playlist_type) sp_playlistcontainer_playlist_type(sp_playlistcontainer *pc, int index); /** * Return the folder name at @a index * * @param[in] pc Playlist container * @param[in] index Index in playlist container. Should be in the interval [0, sp_playlistcontainer_num_playlists() - 1]. * Index should point at a start-folder entry, otherwise the empty string is written to buffer. * @param[in] buffer Pointer to char[] where to store folder name * @param[in] buffer_size Size of array * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INDEX_OUT_OF_RANGE * * @sa sp_session_playlistcontainer() */ SP_LIBEXPORT(sp_error) sp_playlistcontainer_playlist_folder_name(sp_playlistcontainer *pc, int index, char *buffer, int buffer_size); /** * Return the folder id at @a index * * @param[in] pc Playlist container * @param[in] index Index in playlist container. Should be in the interval [0, sp_playlistcontainer_num_playlists() - 1] * * @return The group ID of the folder. Returns 0 on index out of range, pc being NULL or indexed item not being a folder * * @sa sp_session_playlistcontainer() */ SP_LIBEXPORT(sp_uint64) sp_playlistcontainer_playlist_folder_id(sp_playlistcontainer *pc, int index); /** * Add an empty playlist at the end of the playlist container. * The name must not consist of only spaces and it must be shorter than 256 characters. * * @param[in] pc Playlist container * @param[in] name Name of new playlist * * @return Pointer to the new playlist. Can be NULL if the operation fails. */ SP_LIBEXPORT(sp_playlist *) sp_playlistcontainer_add_new_playlist(sp_playlistcontainer *pc, const char *name); /** * Add an existing playlist at the end of the given playlist container * * @param[in] pc Playlist container * @param[in] link Link object pointing to a playlist * * @return Pointer to the new playlist. Will be NULL if the playlist already exists. */ SP_LIBEXPORT(sp_playlist *) sp_playlistcontainer_add_playlist(sp_playlistcontainer *pc, sp_link *link); /** * Remove playlist at index from the given playlist container * * @param[in] pc Playlist container * @param[in] index Index of playlist to be removed * * @return error One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INDEX_OUT_OF_RANGE */ SP_LIBEXPORT(sp_error) sp_playlistcontainer_remove_playlist(sp_playlistcontainer *pc, int index); /** * Move a playlist in the playlist container * * @param[in] pc Playlist container * @param[in] index Index of playlist to be moved * @param[in] new_position New position for the playlist * @param[in] dry_run Do not execute the move, only check if it possible * @return error One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INDEX_OUT_OF_RANGE * SP_ERROR_INVALID_INDATA - If trying to move a folder into itself */ SP_LIBEXPORT(sp_error) sp_playlistcontainer_move_playlist(sp_playlistcontainer *pc, int index, int new_position, bool dry_run); /** * Add a playlist folder * * @param[in] pc Playlist container * @param[in] index Position of SP_PLAYLIST_TYPE_START_FOLDER entry * @param[in] name Name of group * @return error One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_INDEX_OUT_OF_RANGE * * @note This operation will actually create two playlists. One of * type SP_PLAYLIST_TYPE_START_FOLDER and immediately following a * SP_PLAYLIST_TYPE_END_FOLDER one. * * To remove a playlist folder both of these must be deleted or the list * will be left in an inconsistant state. * * There is no way to rename a playlist folder. Instead you need to remove * the folder and recreate it again. */ SP_LIBEXPORT(sp_error) sp_playlistcontainer_add_folder(sp_playlistcontainer *pc, int index, const char *name); /** * Return a pointer to the user object of the owner. * * @param[in] pc Playlist container. * @return The user object or NULL if unknown or none. */ SP_LIBEXPORT(sp_user *) sp_playlistcontainer_owner(sp_playlistcontainer *pc); /** * Increase reference count on playlistconatiner object * * @param[in] pc Playlist container. * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlistcontainer_add_ref(sp_playlistcontainer *pc); /** * Release reference count on playlistconatiner object * * @param[in] pc Playlist container. * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_playlistcontainer_release(sp_playlistcontainer *pc); /** * Get the number of new tracks in a playlist since the corresponding * function sp_playlistcontainer_clear_unseen_tracks() was called. The * function always returns the number of new tracks, and fills the * \p tracks array with the new tracks, but not more than specified in * \p num_tracks. The function will return a negative value on failure. * * @param[in] pc Playlist container. * @param[in] playlist Playlist object. * @param[out] tracks Array of pointer to new tracks (maybe NULL) * @param[in] num_tracks Size of tracks array * @return Returns the number of unseen tracks */ SP_LIBEXPORT(int) sp_playlistcontainer_get_unseen_tracks(sp_playlistcontainer *pc, sp_playlist *playlist, sp_track **tracks, int num_tracks); /** * Clears a playlist from unseen tracks, so that next call to sp_playlistcontainer_get_unseen_tracks() will return 0 until a new track is added to the \p playslist. * * @param[in] pc Playlist container. * @param[in] playlist Playlist object. * @return Returns 0 on success and -1 on failure. */ SP_LIBEXPORT(int) sp_playlistcontainer_clear_unseen_tracks(sp_playlistcontainer *pc, sp_playlist *playlist); /** @} */ /** * @defgroup user User handling * @{ */ /** * User relation type */ typedef enum sp_relation_type { SP_RELATION_TYPE_UNKNOWN = 0, ///< Not yet known SP_RELATION_TYPE_NONE = 1, ///< No relation SP_RELATION_TYPE_UNIDIRECTIONAL = 2, ///< The currently logged in user is following this uer SP_RELATION_TYPE_BIDIRECTIONAL = 3, ///< Bidirectional friendship established } sp_relation_type; /** * Get a pointer to a string representing the user's canonical username. * * @param[in] user The Spotify user whose canonical username you would like a string representation of * * @return A string representing the canonical username. */ SP_LIBEXPORT(const char *) sp_user_canonical_name(sp_user *user); /** * Get a pointer to a string representing the user's displayable username. * If there is no difference between the canonical username and the display name, * or if the library does not know about the display name yet, the canonical username will * be returned. * * @param[in] user The Spotify user whose displayable username you would like a string representation of * * @return A string */ SP_LIBEXPORT(const char *) sp_user_display_name(sp_user *user); /** * Get load status for a user object. Before it is loaded, only the user's canonical username * is known. * * @param[in] user Spotify user object * * @return True if user object is loaded, otherwise false */ SP_LIBEXPORT(bool) sp_user_is_loaded(sp_user *user); /** * Increase the reference count of an user * * @param[in] user The user object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_user_add_ref(sp_user *user); /** * Decrease the reference count of an user * * @param[in] user The user object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_user_release(sp_user *user); /** @} */ /** * @defgroup toplist Toplist handling * @{ */ /** * Toplist types */ typedef enum { SP_TOPLIST_TYPE_ARTISTS = 0, ///< Top artists SP_TOPLIST_TYPE_ALBUMS = 1, ///< Top albums SP_TOPLIST_TYPE_TRACKS = 2, ///< Top tracks } sp_toplisttype; /** * Convenience macro to create a toplist region. Toplist regions are ISO 3166-1 * country codes (in uppercase) encoded in an integer. There are also some reserved * codes used to denote non-country regions. See sp_toplistregion * * Example: SP_TOPLIST_REGION('S', 'E') for Sweden */ #define SP_TOPLIST_REGION(a, b) ((a) << 8 | (b)) /** * Special toplist regions */ typedef enum { SP_TOPLIST_REGION_EVERYWHERE = 0, ///< Global toplist SP_TOPLIST_REGION_USER = 1, ///< Toplist for a given user } sp_toplistregion; /** * The type of a callback used in sp_toplistbrowse_create() * * When the callback is called, the metadata of all tracks belonging to it will have * been loaded, so sp_track_is_loaded() will return non-zero. The same goes for the * similar toplist data. * * @param[in] result The same pointer returned by sp_toplistbrowse_create() * @param[in] userdata The opaque pointer given to sp_toplistbrowse_create() */ typedef void SP_CALLCONV toplistbrowse_complete_cb(sp_toplistbrowse *result, void *userdata); /** * Initiate a request for browsing an toplist * * The user is responsible for freeing the returned toplist browse using sp_toplistbrowse_release(). This can be done in the callback. * * @param[in] session Session object * @param[in] type Type of toplist to be browsed. see the sp_toplisttype enum for possible values * @param[in] region Region. see sp_toplistregion enum. Country specific regions are coded as two chars in an integer. * Sweden would correspond to 'S' << 8 | 'E' * @param[in] username If region is SP_TOPLIST_REGION_USER this specifies which user to get toplists for. NULL means the logged in user. * @param[in] callback Callback to be invoked when browsing has been completed. Pass NULL if you are not interested in this event. * @param[in] userdata Userdata passed to callback. * * @return Toplist browse object * * @see ::toplistbrowse_complete_cb */ SP_LIBEXPORT(sp_toplistbrowse *) sp_toplistbrowse_create(sp_session *session, sp_toplisttype type, sp_toplistregion region, const char *username, toplistbrowse_complete_cb *callback, void *userdata); /** * Check if an toplist browse request is completed * * @param[in] tlb Toplist browse object * * @return True if browsing is completed, false if not */ SP_LIBEXPORT(bool) sp_toplistbrowse_is_loaded(sp_toplistbrowse *tlb); /** * Check if browsing returned an error code. * * @param[in] tlb Toplist browse object * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_IS_LOADING * SP_ERROR_OTHER_PERMANENT * SP_ERROR_OTHER_TRANSIENT */ SP_LIBEXPORT(sp_error) sp_toplistbrowse_error(sp_toplistbrowse *tlb); /** * Increase the reference count of an toplist browse result * * @param[in] tlb The toplist browse result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_toplistbrowse_add_ref(sp_toplistbrowse *tlb); /** * Decrease the reference count of an toplist browse result * * @param[in] tlb The toplist browse result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_toplistbrowse_release(sp_toplistbrowse *tlb); /** * Given an toplist browse object, return number of artists * * @param[in] tlb Toplist browse object * * @return Number of artists on toplist */ SP_LIBEXPORT(int) sp_toplistbrowse_num_artists(sp_toplistbrowse *tlb); /** * Return the artist at the given index in the given toplist browse object * * @param[in] tlb Toplist object * @param[in] index Index of the wanted artist. Should be in the interval [0, sp_toplistbrowse_num_artists() - 1] * * @return The artist at the given index in the given toplist browse object */ SP_LIBEXPORT(sp_artist *) sp_toplistbrowse_artist(sp_toplistbrowse *tlb, int index); /** * Given an toplist browse object, return number of albums * * @param[in] tlb Toplist browse object * * @return Number of albums on toplist */ SP_LIBEXPORT(int) sp_toplistbrowse_num_albums(sp_toplistbrowse *tlb); /** * Return the album at the given index in the given toplist browse object * * @param[in] tlb Toplist object * @param[in] index Index of the wanted album. Should be in the interval [0, sp_toplistbrowse_num_albums() - 1] * * @return The album at the given index in the given toplist browse object */ SP_LIBEXPORT(sp_album *) sp_toplistbrowse_album(sp_toplistbrowse *tlb, int index); /** * Given an toplist browse object, return number of tracks * * @param[in] tlb Toplist browse object * * @return Number of tracks on toplist */ SP_LIBEXPORT(int) sp_toplistbrowse_num_tracks(sp_toplistbrowse *tlb); /** * Return the track at the given index in the given toplist browse object * * @param[in] tlb Toplist object * @param[in] index Index of the wanted track. Should be in the interval [0, sp_toplistbrowse_num_tracks() - 1] * * @return The track at the given index in the given toplist browse object */ SP_LIBEXPORT(sp_track *) sp_toplistbrowse_track(sp_toplistbrowse *tlb, int index); /** * Return the time (in ms) that was spent waiting for the Spotify backend to serve the request * * @param[in] tlb Toplist object * * @return -1 if the request was served from the local cache * If the result is not yet loaded the return value is undefined */ SP_LIBEXPORT(int) sp_toplistbrowse_backend_request_duration(sp_toplistbrowse *tlb); /** @} */ /** * @defgroup inbox Inbox subsystem * @{ */ /** * The type of a callback used in sp_inbox_post() * * When this callback is called, the sp_track_is_loaded(), sp_album_is_loaded(), * and sp_artist_is_loaded() functions will return non-zero for the objects * contained in the search result. * * @param[in] result The same pointer returned by sp_search_create() * @param[in] userdata The opaque pointer given to sp_search_create() */ typedef void SP_CALLCONV inboxpost_complete_cb(sp_inbox *result, void *userdata); /** * Add to inbox * * @param[in] session Session object * @param[in] user Canonical username of recipient * @param[in] tracks Array of tracks to post * @param[in] num_tracks Number of tracks in \p tracks * @param[in] message Message to attach to tracks. UTF-8 * @param[in] callback Callback to be invoked when the request has completed * @param[in] userdata Userdata passed to callback * * @return sp_inbox object if the request has been sent, NULL if request failed to initialize */ SP_LIBEXPORT(sp_inbox *) sp_inbox_post_tracks(sp_session *session, const char *user, sp_track * const *tracks, int num_tracks, const char *message, inboxpost_complete_cb *callback, void *userdata); /** * Check if inbox operation returned an error code. * * @param[in] inbox Inbox object * * @return One of the following errors, from ::sp_error * SP_ERROR_OK * SP_ERROR_OTHER_TRANSIENT * SP_ERROR_PERMISSION_DENIED * SP_ERROR_INVALID_INDATA * SP_ERROR_INBOX_IS_FULL * SP_ERROR_NO_SUCH_USER * SP_ERROR_OTHER_PERMANENT */ SP_LIBEXPORT(sp_error) sp_inbox_error(sp_inbox *inbox); /** * Increase the reference count of a inbox result * * @param[in] inbox The inbox result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_inbox_add_ref(sp_inbox *inbox); /** * Decrease the reference count of a inbox result * * @param[in] inbox The inbox result object * @return One of the following errors, from ::sp_error * SP_ERROR_OK */ SP_LIBEXPORT(sp_error) sp_inbox_release(sp_inbox *inbox); /** @} */ /** * Return the libspotify build ID * * This might be useful to have available for display somewhere in your * user interface. */ SP_LIBEXPORT(const char *) sp_build_id(void); #ifdef __cplusplus } #endif #endif /* PUBLIC_API_H */ /** * @example browse.c * * The browse.c example shows how you can use the album, artist, and browse functions. * The example also include some rudimentary playlist browsing. * It is part of the spshell program */ /** * @example search.c * * The search.c example shows how you can use search functions. * It is part of the spshell program */ /** * @example toplist.c * * The toplist.c example shows how you can use toplist functions. * It is part of the spshell program */ /** * @example jukebox.c * * The jukebox.c example shows how you can use playback and playlist functions. */ ================================================ FILE: libspotify/licenses.xhtml ================================================ Third-party licenses

Third-party licenses

Several fantastic pieces of free and open-source software have really helped get Spotify to where it is today. A few require that we include their license agreements within our product. Consider it done. As we enjoy giving credit where it's due, we included the entire list below. This means you can not only see which software we've been using, but the terms of the licenses too. A big thanks from all of us at Spotify to the smart people behind the fantastic programs listed. Rock on!


Boost

http://www.boost.org/

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following:

The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Expat

http://www.jclark.com/xml/expat.html

Expat License. Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


FastDelegate

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CODE PROJECT OPEN LICENSE ("LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.

BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HEREIN, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE AUTHOR GRANTS YOU THE RIGHTS CONTAINED HEREIN IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. IF YOU DO NOT AGREE TO ACCEPT AND BE BOUND BY THE TERMS OF THIS LICENSE, YOU CANNOT MAKE ANY USE OF THE WORK.

  1. Definitions.
    1. "Articles" means, collectively, all articles written by Author which describes how the Source Code and Executable Files for the Work may be used by a user.
    2. "Author" means the individual or entity that offers the Work under the terms of this License.
    3. "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works.
    4. "Executable Files" refer to the executables, binary files, configuration and any required data files included in the Work.
    5. "Publisher" means the provider of the website, magazine, CD-ROM, DVD or other medium from or by which the Work is obtained by You.
    6. "Source Code" refers to the collection of source code and configuration files used to create the Executable Files.
    7. "Standard Version" refers to such a Work if it has not been modified, or has been modified in accordance with the consent of the Author, such consent being in the full discretion of the Author.
    8. "Work" refers to the collection of files distributed by the Publisher, including the Source Code, Executable Files, binaries, data files, documentation, whitepapers and the Articles.
    9. "You" is you, an individual or entity wishing to use the Work and exercise your rights under this License.
  2. Fair Use/Fair Use Rights. Nothing in this License is intended to reduce, limit, or restrict any rights arising from fair use, fair dealing, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.
  3. License Grant. Subject to the terms and conditions of this License, the Author hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
    1. You may use the standard version of the Source Code or Executable Files in Your own applications.
    2. You may apply bug fixes, portability fixes and other modifications obtained from the Public Domain or from the Author. A Work modified in such a way shall still be considered the standard version and will be subject to this License.
    3. You may otherwise modify Your copy of this Work (excluding the Articles) in any way to create a Derivative Work, provided that You insert a prominent notice in each changed file stating how, when and where You changed that file.
    4. You may distribute the standard version of the Executable Files and Source Code or Derivative Work in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution.
    5. The Articles discussing the Work published in any form by the author may not be distributed or republished without the Author's consent. The author retains copyright to any such Articles. You may use the Executable Files and Source Code pursuant to this License but you may not repost or republish or otherwise distribute or make available the Articles, without the prior written consent of the Author.
    Any subroutines or modules supplied by You and linked into the Source Code or Executable Files this Work shall not be considered part of this Work and will not be subject to the terms of this License.
  4. Patent License. Subject to the terms and conditions of this License, each Author hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, import, and otherwise transfer the Work.
  5. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
    1. You agree not to remove any of the original copyright, patent, trademark, and attribution notices and associated disclaimers that may appear in the Source Code or Executable Files.
    2. You agree not to advertise or in any way imply that this Work is a product of Your own.
    3. The name of the Author may not be used to endorse or promote products derived from the Work without the prior written consent of the Author.
    4. You agree not to sell, lease, or rent any part of the Work. This does not restrict you from including the Work or any part of the Work inside a larger software distribution that itself is being sold. The Work by itself, though, cannot be sold, leased or rented.
    5. You may distribute the Executable Files and Source Code only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy of the Executable Files or Source Code You distribute and ensure that anyone receiving such Executable Files and Source Code agrees that the terms of this License apply to such Executable Files and/or Source Code. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute the Executable Files or Source Code with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License.
    6. You agree not to use the Work for illegal, immoral or improper purposes, or on pages containing illegal, immoral or improper material. The Work is subject to applicable export laws. You agree to comply with all such laws and regulations that may apply to the Work after Your receipt of the Work.
  6. Representations, Warranties and Disclaimer. THIS WORK IS PROVIDED "AS IS", "WHERE IS" AND "AS AVAILABLE", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES OR CONDITIONS OR GUARANTEES. YOU, THE USER, ASSUME ALL RISK IN ITS USE, INCLUDING COPYRIGHT INFRINGEMENT, PATENT INFRINGEMENT, SUITABILITY, ETC. AUTHOR EXPRESSLY DISCLAIMS ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS, INCLUDING WITHOUT LIMITATION, WARRANTIES OR CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY OR FITNESS FOR A PARTICULAR PURPOSE, OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT, OR THAT THE WORK (OR ANY PORTION THEREOF) IS CORRECT, USEFUL, BUG-FREE OR FREE OF VIRUSES. YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS.
  7. Indemnity. You agree to defend, indemnify and hold harmless the Author and the Publisher from and against any claims, suits, losses, damages, liabilities, costs, and expenses (including reasonable legal or attorneys’ fees) resulting from or relating to any use of the Work by You.
  8. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL THE AUTHOR OR THE PUBLISHER BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK OR OTHERWISE, EVEN IF THE AUTHOR OR THE PUBLISHER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
  9. Termination.
    1. This License and the rights granted hereunder will terminate automatically upon any breach by You of any term of this License. Individuals or entities who have received Derivative Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 6, 7, 8, 9, 10 and 11 will survive any termination of this License.
    2. If You bring a copyright, trademark, patent or any other infringement claim against any contributor over infringements You claim are made by the Work, your License from such contributor to the Work ends automatically.
    3. Subject to the above terms and conditions, this License is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, the Author reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
  10. Publisher. The parties hereby confirm that the Publisher shall not, under any circumstances, be responsible for and shall not have any liability in respect of the subject matter of this License. The Publisher makes no warranty whatsoever in connection with the Work and shall not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. The Publisher reserves the right to cease making the Work available to You at any time without notice
  11. Miscellaneous
    1. This License shall be governed by the laws of the location of the head office of the Author or if the Author is an individual, the laws of location of the principal place of residence of the Author.
    2. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this License, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
    3. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
    4. This License constitutes the entire agreement between the parties with respect to the Work licensed herein. There are no understandings, agreements or representations with respect to the Work not specified herein. The Author shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Author and You

libogg

http://www.xiph.org/ogg/

Copyright (c) 2002, Xiph.org Foundation

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • Neither the name of the Xiph.org Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


libvorbis

http://www.xiph.org/vorbis/

Copyright (c) 2002-2004 Xiph.org Foundation

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • Neither the name of the Xiph.org Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Tremolo

http://wss.co.uk/pinknoise/tremolo

Copyright (C) 2002-2009 Xiph.org Foundation Changes Copyright (C) 2009-2010 Robin Watts for Pinknoise Productions Ltd

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

- Neither the name of the Xiph.org Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Tremor

http://wiki.xiph.org/index.php/Tremor

Copyright (c) 2002, Xiph.org Foundation

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

- Neither the name of the Xiph.org Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Mersenne Twister

http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/CODES/mt19937ar.c

Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


zlib

http://www.zlib.net/

zlib.h -- interface of the 'zlib' general purpose compression library version 1.2.3, July 18th, 2005

Copyright (C) 1995-2004 Jean-loup Gailly and Mark Adler

This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.

Jean-loup Gailly jloup@gzip.org, Mark Adler madler@alumni.caltech.edu


cURL

http://curl.haxx.se

COPYRIGHT AND PERMISSION NOTICE

Copyright (c) 1996 - 2011, Daniel Stenberg, <daniel@haxx.se>.

All rights reserved.

Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder.


c-ares

http://c-ares.haxx.se

Copyright 1998 by the Massachusetts Institute of Technology.

Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of M.I.T. not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. M.I.T. makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.

================================================ FILE: licenses/JSON for Modern C++.txt ================================================ MIT License Copyright (c) 2013-2018 Niels Lohmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: licenses/LibSpotify.txt ================================================ For the current terms and conditions, please read: http://developer.spotify.com/en/libspotify/terms-of-use/ ================================================ FILE: licenses/PFC.txt ================================================ Copyright (C) 2002-2019 Peter Pawlowski This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ================================================ FILE: licenses/WTL.txt ================================================ Microsoft Public License (MS-PL) This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 1. Definitions The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to the software. A "contributor" is any person that distributes its contribution under this license. "Licensed patents" are a contributor's patent claims that read directly on its contribution. 2. Grant of Rights (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 3. Conditions and Limitations (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. ================================================ FILE: licenses/fmt.txt ================================================ Copyright (c) 2012 - present, Victor Zverovich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- Optional exception to the license --- As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into a machine-executable object form of such source code, you may redistribute such embedded portions in such object form without including the above copyright and permission notices. ================================================ FILE: licenses/foobar2000 SDK.txt ================================================ foobar2000 1.5 SDK Copyright (c) 2001-2019, Peter Pawlowski All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Note that separate less restrictive licenses apply to the included 'pfc' and 'libPPUI' libraries. See license files included in respective folders for details. ================================================ FILE: licenses/range-v3.txt ================================================ ======================================================== Boost Software License - Version 1.0 - August 17th, 2003 ======================================================== Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================================== libc++ License ============================================================================== The libc++ library is dual licensed under both the University of Illinois "BSD-Like" license and the MIT license. As a user of this code you may choose to use it under either license. As a contributor, you agree to allow your code to be used under both. Full text of the relevant licenses is included below. ============================================================================== University of Illinois/NCSA Open Source License Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT http://llvm.org/svn/llvm-project/libcxx/trunk/CREDITS.TXT All rights reserved. Developed by: LLVM Team University of Illinois at Urbana-Champaign http://llvm.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution. * Neither the names of the LLVM Team, University of Illinois at Urbana-Champaign, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. ============================================================================== Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT http://llvm.org/svn/llvm-project/libcxx/trunk/CREDITS.TXT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================================== Stepanov and McJones, "Elements of Programming" license ============================================================================== // Copyright (c) 2009 Alexander Stepanov and Paul McJones // // Permission to use, copy, modify, distribute and sell this software // and its documentation for any purpose is hereby granted without // fee, provided that the above copyright notice appear in all copies // and that both that copyright notice and this permission notice // appear in supporting documentation. The authors make no // representations about the suitability of this software for any // purpose. It is provided "as is" without express or implied // warranty. // // Algorithms from // Elements of Programming // by Alexander Stepanov and Paul McJones // Addison-Wesley Professional, 2009 ============================================================================== SGI C++ Standard Template Library license ============================================================================== // Copyright (c) 1994 // Hewlett-Packard Company // // Permission to use, copy, modify, distribute and sell this software // and its documentation for any purpose is hereby granted without fee, // provided that the above copyright notice appear in all copies and // that both that copyright notice and this permission notice appear // in supporting documentation. Hewlett-Packard Company makes no // representations about the suitability of this software for any // purpose. It is provided "as is" without express or implied warranty. // // Copyright (c) 1996 // Silicon Graphics Computer Systems, Inc. // // Permission to use, copy, modify, distribute and sell this software // and its documentation for any purpose is hereby granted without fee, // provided that the above copyright notice appear in all copies and // that both that copyright notice and this permission notice appear // in supporting documentation. Silicon Graphics makes no // representations about the suitability of this software for any // purpose. It is provided "as is" without express or implied warranty. // ================================================ FILE: licenses/ring-span-lite.txt ================================================ Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: licenses/span-lite.txt ================================================ Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: props/local_dependencies/libspotify.props ================================================  <_PropertySheetDisplayName>libspotify $(MainDir)libspotify\ $(LibSpotifyDirectory)lib\ $(LibSpotifyDirectory)include\ libspotify.lib $(LibSpotifyIncludeDirectory);$(IncludePath) $(LibSpotifyLibDirectory);%(AdditionalLibraryDirectories) $(LibSpotifyDependencies);%(AdditionalDependencies) ================================================ FILE: props/submodules/fb2k_utils.props ================================================  <_PropertySheetDisplayName>fb2k_utils $(SolutionDir)..\submodules\fb2k_utils\ $(QwrFb2kUtilsRootDir)props\ $(QwrFb2kUtilsRootDir)src\;$(IncludePath) ================================================ FILE: props/submodules/submodules.props ================================================  <_PropertySheetDisplayName>submodules $(SolutionDir)..\submodules\ $(SubmodulesDirectory);$(IncludePath) ================================================ FILE: scripts/README.md ================================================ ### Main scripts - setup.py - Set up everything, so that project can be built. - pack_component.py - Pack project binaries to .fb2k-component archive. - generate_docs.py - Generate HTML docs from JsDoc docs. ### Auxiliary scripts - update_submodules.py - Update submodules to their latest version. - update_gh_pages.py - Update gh-pages folder with latest generated content. ================================================ FILE: scripts/call_wrapper.py ================================================ #!/usr/bin/env python3 import traceback import sys class SkippedError(Exception): def __init__(self): self.message = "Skipped error" def final_call_decorator(start_msg: str, success_msg: str, failure_msg: str): def f_decorator(f): def wrapper(*args, **kwds): print(start_msg) try: f(*args, **kwds) print(success_msg) sys.exit(0) except SkippedError: print(failure_msg, file=sys.stderr) sys.exit(1) except Exception: traceback.print_exc(file=sys.stderr) print(failure_msg, file=sys.stderr) sys.exit(1) return wrapper return f_decorator ================================================ FILE: scripts/download_submodules.py ================================================ #!/usr/bin/env python3 import subprocess from pathlib import Path import call_wrapper def download_submodule(root_dir, submodule_name): print(f"Downloading {submodule_name}...") try: subprocess.check_call(f"git submodule update --init --depth=10 -- submodules/{submodule_name}", cwd=root_dir, shell=True) except subprocess.CalledProcessError: try: subprocess.check_call(f"git submodule update --init --depth=50 -- submodules/{submodule_name}", cwd=root_dir, shell=True) except subprocess.CalledProcessError: # Shallow copy does not honour default branch config subprocess.check_call("git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/*", cwd=root_dir/"submodules"/submodule_name, shell=True) subprocess.check_call(f"git submodule deinit --force -- submodules/{submodule_name}", cwd=root_dir, shell=True) subprocess.check_call(f"git submodule update --init --force -- submodules/{submodule_name}", cwd=root_dir, shell=True) def download(): cur_dir = Path(__file__).parent.absolute() root_dir = cur_dir.parent subprocess.check_call("git submodule sync", cwd=root_dir, shell=True) subprocess.check_call("git submodule foreach git reset --hard", cwd=root_dir, shell=True) for subdir in [f for f in (root_dir/"submodules").iterdir() if f.is_dir()]: download_submodule(root_dir, subdir.name) if __name__ == '__main__': call_wrapper.final_call_decorator( "Downloading submodules", "Downloading submodules: success", "Downloading submodules: failure!" )(download)() ================================================ FILE: scripts/pack_component.py ================================================ #!/usr/bin/env python3 import argparse import zipfile from pathlib import Path from zipfile import ZipFile import call_wrapper def path_basename_tuple(path): return (path, path.name) def zipdir(zip_file, path, arc_path): assert(path.exists() and path.is_dir()) for file in path.rglob('*'): if (file.name.startswith('.')): # skip `hidden` files continue if (arc_path): file_arc_path = f'{arc_path}/{file.relative_to(path)}' else: file_arc_path = file.relative_to(path) zip_file.write(file, file_arc_path) def pack(is_debug = False): cur_dir = Path(__file__).parent.absolute() root_dir = cur_dir.parent result_machine_dir = root_dir/'_result'/('Win32_Debug' if is_debug else 'Win32_Release') assert(result_machine_dir.exists() and result_machine_dir.is_dir()) output_dir = result_machine_dir output_dir.mkdir(parents=True, exist_ok=True) component_zip = output_dir/'foo_spotify.fb2k-component' component_zip.unlink(missing_ok=True) with ZipFile(component_zip, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as z: zipdir(z, root_dir/'licenses', 'licenses') z.write(*path_basename_tuple(root_dir/'libspotify'/'lib'/'libspotify.dll')) z.write(*path_basename_tuple(root_dir/'LICENSE')) z.write(*path_basename_tuple(root_dir/'CHANGELOG.md')) z.write(*path_basename_tuple(result_machine_dir/'bin'/'foo_spotify.dll')) if (is_debug): # Only debug package should have pdbs inside z.write(*path_basename_tuple(result_machine_dir/'dbginfo'/'foo_spotify.pdb')) print(f'Generated file: {component_zip}') if (not is_debug): # Release pdbs are packed in a separate package pdb_zip = output_dir/'foo_spotify_pdb.zip' if (pdb_zip.exists()): pdb_zip.unlink() with ZipFile(pdb_zip, 'w', zipfile.ZIP_DEFLATED) as z: z.write(*path_basename_tuple(result_machine_dir/'dbginfo'/'foo_spotify.pdb')) print(f'Generated file: {pdb_zip}') if __name__ == '__main__': parser = argparse.ArgumentParser(description='Pack component to .fb2k-component') parser.add_argument('--debug', default=False, action='store_true') args = parser.parse_args() call_wrapper.final_call_decorator( 'Packing component', 'Packing component: success', 'Packing component: failure!' )( pack )( args.debug ) ================================================ FILE: scripts/setup.py ================================================ #!/usr/bin/env python3 import argparse import importlib import importlib.util import sys import traceback from pathlib import Path from typing import Union import call_wrapper import download_submodules PathLike = Union[str, Path] def call_decorator(command_name: str): def f_decorator(f): def wrapper(*args, **kwds): print(f'>> {command_name}: starting') try: f(*args, **kwds) print(f'<< {command_name}: success') except Exception: traceback.print_exc(file=sys.stderr) print(f'<< {command_name}: failure', file=sys.stderr) raise call_wrapper.SkippedError() return wrapper return f_decorator def load_module(script_path): spec = importlib.util.spec_from_file_location("module.name", script_path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod def setup( skip_submodules_download, skip_submodules_patches ): cur_dir = Path(__file__).parent.absolute() root_dir = cur_dir.parent scripts_path = root_dir/'submodules'/'fb2k_utils'/'scripts' if (not skip_submodules_download): call_decorator('Downloading submodules')(download_submodules.download)() if (not skip_submodules_patches): call_decorator('Patching fb2k submodules')( load_module(scripts_path/'patch_fb2k_submodules.py').patch )( root_dir=root_dir ) call_decorator('Version header generation')( load_module(scripts_path/'generate_version_header.py').generate_header_custom )( repo_dir=root_dir, output_dir=root_dir/'_result'/'AllPlatforms'/'generated', component_prefix='SPTF' ) call_decorator('Commit hash header generation')( load_module(scripts_path/'generate_commit_hash_header.py').generate_header_custom )( repo_dir=root_dir, output_dir=root_dir/'_result'/'AllPlatforms'/'generated', component_prefix='SPTF' ) call_decorator('SourceLink configuration file generation' )( load_module(scripts_path/'generate_source_link_config.py').generate_config_custom )( repo_dir=root_dir, output_dir=root_dir/'_result'/'AllPlatforms'/'generated', repo='theqwertiest/foo_spotify' ) call_decorator('3rd-party notices generation' )( load_module(scripts_path/'generate_third_party.py').generate )( root_dir=root_dir, component_name='Spotify Integration' ) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Setup project') parser.add_argument('--skip_submodules_download', default=False, action='store_true') parser.add_argument('--skip_submodules_patches', default=False, action='store_true') args = parser.parse_args() call_wrapper.final_call_decorator( 'Preparing project repo', 'Setup complete!', 'Setup failed!' )( setup )( args.skip_submodules_download, args.skip_submodules_patches ) ================================================ FILE: scripts/update_gh_pages.py ================================================ #!/usr/bin/env python3 import argparse import shutil from pathlib import Path from typing import Union import call_wrapper PathLike = Union[str, Path] def update(gh_pages_dir: PathLike): cur_dir = Path(__file__).parent.absolute() root_dir = cur_dir.parent gh_pages_dir = Path(gh_pages_dir).resolve() assert(gh_pages_dir.exists() and gh_pages_dir.is_dir()) ghp_gen_dir = gh_pages_dir/"assets"/"generated_files" if (ghp_gen_dir.exists()): shutil.rmtree(ghp_gen_dir) ghp_gen_dir.mkdir(parents=True) shutil.copytree(root_dir/"licenses", ghp_gen_dir/"licenses", dirs_exist_ok=True) ghp_inc_dir = gh_pages_dir/"_includes" ghp_inc_dir.mkdir(parents=True, exist_ok=True) shutil.copy2(root_dir/"CHANGELOG.md", gh_pages_dir/"_includes"/"CHANGELOG.md") if __name__ == '__main__': cur_dir = Path(__file__).parent.absolute() root_dir = cur_dir.parent gh_pages_dir = root_dir/"gh-pages" parser = argparse.ArgumentParser(description='Update GitHub Pages content') parser.add_argument('--gh_pages_dir', default=gh_pages_dir) args = parser.parse_args() call_wrapper.final_call_decorator( "Updating GitHub Pages content", "Updating GitHub Pages content: success", "Updating GitHub Pages content: failure!" )( update )( gh_pages_dir ) ================================================ FILE: workspaces/foo_spotify.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30225.117 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foo_spotify", "..\foo_spotify\foo_spotify.vcxproj", "{FB107A12-DEFC-4782-97FB-EC155B13550C}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_SDK", "..\submodules\foobar2000\SDK\foobar2000_SDK.vcxproj", "{E8091321-D79D-4575-86EF-064EA1A4A20D}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pfc", "..\submodules\pfc\pfc.vcxproj", "{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_component_client", "..\submodules\foobar2000\foobar2000_component_client\foobar2000_component_client.vcxproj", "{71AD2674-065B-48F5-B8B0-E1F9D3892081}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C179572D-BD48-46D8-98D9-3CD4354A1A6C}" ProjectSection(SolutionItems) = preProject ..\foo_spotify\.clang-format = ..\foo_spotify\.clang-format EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fb2k_utils", "..\submodules\fb2k_utils\src\fb2k_utils.vcxproj", "{EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{6A9AA2D7-2F76-4BD1-80EE-87E4B9DA70DB}" ProjectSection(SolutionItems) = preProject ..\CHANGELOG.md = ..\CHANGELOG.md ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "submodules", "submodules", "{096E7B28-E3A6-4685-BA4F-F92FB4091EF2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug FB2K|Win32 = Debug FB2K|Win32 Debug FB2K|x64 = Debug FB2K|x64 Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release FB2K|Win32 = Release FB2K|Win32 Release FB2K|x64 = Release FB2K|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FB107A12-DEFC-4782-97FB-EC155B13550C}.Debug FB2K|Win32.ActiveCfg = Debug|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Debug FB2K|Win32.Build.0 = Debug|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Debug FB2K|x64.ActiveCfg = Release|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Debug FB2K|x64.Build.0 = Release|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Debug|Win32.ActiveCfg = Debug|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Debug|Win32.Build.0 = Debug|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Debug|x64.ActiveCfg = Debug|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Release FB2K|Win32.ActiveCfg = Release|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Release FB2K|Win32.Build.0 = Release|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Release FB2K|x64.ActiveCfg = Release|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Release FB2K|x64.Build.0 = Release|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Release|Win32.ActiveCfg = Release|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Release|Win32.Build.0 = Release|Win32 {FB107A12-DEFC-4782-97FB-EC155B13550C}.Release|x64.ActiveCfg = Release|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug FB2K|Win32.ActiveCfg = Debug|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug FB2K|Win32.Build.0 = Debug|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug FB2K|x64.ActiveCfg = Release|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug FB2K|x64.Build.0 = Release|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|Win32.ActiveCfg = Debug|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|Win32.Build.0 = Debug|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x64.ActiveCfg = Debug|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release FB2K|Win32.ActiveCfg = Release|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release FB2K|Win32.Build.0 = Release|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release FB2K|x64.ActiveCfg = Release|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release FB2K|x64.Build.0 = Release|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|Win32.ActiveCfg = Release|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|Win32.Build.0 = Release|Win32 {E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x64.ActiveCfg = Release|Win32 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug FB2K|Win32.ActiveCfg = Debug FB2K|Win32 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug FB2K|Win32.Build.0 = Debug FB2K|Win32 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug FB2K|x64.ActiveCfg = Debug FB2K|x64 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug FB2K|x64.Build.0 = Debug FB2K|x64 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|Win32.ActiveCfg = Debug|Win32 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|Win32.Build.0 = Debug|Win32 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x64.ActiveCfg = Debug|x64 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x64.Build.0 = Debug|x64 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release FB2K|Win32.ActiveCfg = Release FB2K|Win32 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release FB2K|Win32.Build.0 = Release FB2K|Win32 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release FB2K|x64.ActiveCfg = Release FB2K|x64 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release FB2K|x64.Build.0 = Release FB2K|x64 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|Win32.ActiveCfg = Release|Win32 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|Win32.Build.0 = Release|Win32 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x64.ActiveCfg = Release|x64 {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x64.Build.0 = Release|x64 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug FB2K|Win32.ActiveCfg = Debug|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug FB2K|Win32.Build.0 = Debug|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug FB2K|x64.ActiveCfg = Release|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug FB2K|x64.Build.0 = Release|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|Win32.ActiveCfg = Debug|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|Win32.Build.0 = Debug|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x64.ActiveCfg = Debug|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release FB2K|Win32.ActiveCfg = Release|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release FB2K|Win32.Build.0 = Release|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release FB2K|x64.ActiveCfg = Release|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release FB2K|x64.Build.0 = Release|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|Win32.ActiveCfg = Release|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|Win32.Build.0 = Release|Win32 {71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x64.ActiveCfg = Release|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Debug FB2K|Win32.ActiveCfg = Debug|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Debug FB2K|Win32.Build.0 = Debug|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Debug FB2K|x64.ActiveCfg = Release|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Debug FB2K|x64.Build.0 = Release|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Debug|Win32.ActiveCfg = Debug|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Debug|Win32.Build.0 = Debug|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Debug|x64.ActiveCfg = Debug|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Release FB2K|Win32.ActiveCfg = Release|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Release FB2K|Win32.Build.0 = Release|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Release FB2K|x64.ActiveCfg = Release|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Release FB2K|x64.Build.0 = Release|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Release|Win32.ActiveCfg = Release|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Release|Win32.Build.0 = Release|Win32 {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571}.Release|x64.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {E8091321-D79D-4575-86EF-064EA1A4A20D} = {096E7B28-E3A6-4685-BA4F-F92FB4091EF2} {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C} = {096E7B28-E3A6-4685-BA4F-F92FB4091EF2} {71AD2674-065B-48F5-B8B0-E1F9D3892081} = {096E7B28-E3A6-4685-BA4F-F92FB4091EF2} {EE3BF4F9-2014-4CFF-96C8-44CFB85E0571} = {096E7B28-E3A6-4685-BA4F-F92FB4091EF2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {869A994B-3306-4E73-A266-A14CF217581C} EndGlobalSection EndGlobal