diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..df15180 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,174 @@ + +#---------------------------------# +# 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_ACCESS_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: + - 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 + - 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_ACCESS_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: + +# on build failure +on_failure: + +# after build failure or success +on_finish: + +#---------------------------------# +# notifications # +#---------------------------------# + +notifications: diff --git a/.bandit b/.bandit new file mode 100644 index 0000000..8ba1afd --- /dev/null +++ b/.bandit @@ -0,0 +1 @@ +skips: ['B101','B404','B602','B607'] diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 0000000..f6b8cb4 --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,9 @@ +engines: + prospector: + enabled: true + python_version: 3 + pylint: + enabled: true + python_version: 3 +exclude_paths: + - "libspotify/**" \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..622004f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,19 @@ +# 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14352b --- /dev/null +++ b/.gitignore @@ -0,0 +1,275 @@ +# 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/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2f1e1b3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,29 @@ +[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 diff --git a/.license_index.txt b/.license_index.txt new file mode 100644 index 0000000..6c642ee --- /dev/null +++ b/.license_index.txt @@ -0,0 +1,8 @@ +foobar2000 SDK: other +fmt: other +JSON for Modern C++: MIT +LibSpotify: other +PFC: zlib +range-v3: BSL-1.0 +span lite: BSL-1.0 +WTL: MS-PL diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1dff5e2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +#### Table of Contents +- [Unreleased](#unreleased) + +___ + +## [Unreleased][] + +[unreleased]: https://github.com/TheQwertiest/foo_spotify/commits/master diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..79e9eda --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..51128b6 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# 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://app.codacy.com/app/qwertiest/foo_spotify?utm_source=github.com&utm_medium=referral&utm_content=TheQwertiest/foo_spotify&utm_campaign=Badge_Grade_Dashboard) + +This is a component for the [foobar2000](https://www.foobar2000.org) audio player, which allows to play tracks from Spotify. + +Features: +- Import of albums and playlists. +- Album art fetching. +- [foo_acfu](https://acfu.3dyd.com) integration. + +## Prerequisites + +- Windows 7 or later. +- foobar2000 v1.3.17 or later. +- Spotify Premium account. + +## Installation + +1. Remove `foo_spotify_input` component if present. +1. Download the [latest release](https://github.com/TheQwertiest/foo_spotify/releases/latest) (you only need `foo_spotify.fb2k-component`). +1. Install the component using the [following instructions](http://wiki.hydrogenaud.io/index.php?title=Foobar2000:How_to_install_a_component). +1. Authorize the component via `Preferences`>`Spotify Integration`. + +## FAQ + +#### Why two separate authentications are required? + +The component uses two separate interfaces for Spotify integration: +- `LibSpotify` for music playback. +- `WebAPI` for everything else. + +Each one uses a separate authentication scheme. + +#### Why I can't play and/or see some tracks? + +Spotify interface limitation: podcasts and radios are not supported. + +#### Why I can't control playback from Spotify website? +#### Why I can't set foobar2000 as output device from Spotify website? + +Spotify interface limitation: these features are part of the Spotify Connect interface, which is not publicly accessible. + + +## Links +[Changelog][changelog] +[Current tasks and plans][todo] +[Nightly build](https://ci.appveyor.com/api/projects/theqwertiest/foo-spotify/artifacts/_result%2FWin32_Release%2Ffoo_spotify.fb2k-component?branch=master&job=Configuration%3A%20Release) + +## Credits + +- [Respective authors](THIRD_PARTY_NOTICES.md) of the code being used in this project. + +[changelog]: CHANGELOG.md +[todo]: https://github.com/TheQwertiest/foo_spider_monkey_panel/projects/1 +[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://api.codacy.com/project/badge/Grade/319298ca5bd64a739d1e70e3e27d59ab +[codefactor_badge]: https://www.codefactor.io/repository/github/theqwertiest/foo_spotify/badge/master diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..7f4ab11 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,12 @@ +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) +- [fmt - other](licenses/fmt) +- [JSON for Modern C++ - MIT](licenses/JSON%20for%20Modern%20C%2B%2B) +- [LibSpotify - other](licenses/LibSpotify) +- [PFC - zlib](licenses/PFC) +- [range-v3 - BSL-1.0](licenses/range-v3) +- [span lite - BSL-1.0](licenses/span%20lite) +- [WTL - MS-PL](licenses/WTL) diff --git a/foo_spotify/.clang-format b/foo_spotify/.clang-format new file mode 100644 index 0000000..867693e --- /dev/null +++ b/foo_spotify/.clang-format @@ -0,0 +1,78 @@ +--- +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 + +... diff --git a/foo_spotify/.clang-tidy b/foo_spotify/.clang-tidy new file mode 100644 index 0000000..6805209 --- /dev/null +++ b/foo_spotify/.clang-tidy @@ -0,0 +1,31 @@ +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' diff --git a/foo_spotify/backend/audio_buffer.cpp b/foo_spotify/backend/audio_buffer.cpp new file mode 100644 index 0000000..79a1a77 --- /dev/null +++ b/foo_spotify/backend/audio_buffer.cpp @@ -0,0 +1,112 @@ +#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 diff --git a/foo_spotify/backend/audio_buffer.h b/foo_spotify/backend/audio_buffer.h new file mode 100644 index 0000000..1443050 --- /dev/null +++ b/foo_spotify/backend/audio_buffer.h @@ -0,0 +1,91 @@ +#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 diff --git a/foo_spotify/backend/libspotify_backend.cpp b/foo_spotify/backend/libspotify_backend.cpp new file mode 100644 index 0000000..4822e60 --- /dev/null +++ b/foo_spotify/backend/libspotify_backend.cpp @@ -0,0 +1,498 @@ +#include + +#include "libspotify_backend.h" + +#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_; + + // config is main thread only + /* + pfc::string8 proxy; + sptf::config::advanced::network_proxy.get( proxy ); + if ( !proxy.is_empty() ) + { + config_.proxy = proxy.c_str(); + } + */ + + /* + config_.proxy_username = ...; + config_.proxy_password = ...; + 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_, 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_, 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 ); + +#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(); +} + +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, abort_callback& abort ) +{ + 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 ); + } + + retStatus = WaitForLoginStatusUpdate( abort ); + } 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::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 ); +} + +std::string LibSpotify_Backend::GetLoggedInUserName() +{ + { + std::lock_guard lock( loginMutex_ ); + if ( loginStatus_ != LoginStatus::logged_in ) + { + return ""; + } + } + + std::lock_guard lock( apiMutex_ ); + // TODO: add sp_user_display_name + const char* userName = sp_session_user_name( pSpSession_ ); + if ( !userName ) + { + return ""; + } + + return userName; +} + +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(); +} + +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 + { + ::fb2k::inMainThread2( [&, error] { + std::lock_guard lock( loginMutex_ ); + + qwr::ReportErrorWithPopupInMainThread( SPTF_NAME, fmt::format( "Failed to login:\n{}", sp_error_message( error ) ) ); + loginStatus_ = LoginStatus::uninitialized; + } ); + } +} + +void LibSpotify_Backend::message_to_user( const char* message ) +{ + qwr::ReportErrorWithPopupInMainThread( 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; + } + + 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() +{ + qwr::ReportErrorWithPopupInMainThread( 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(); + 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; + } + } +} + +} // namespace sptf diff --git a/foo_spotify/backend/libspotify_backend.h b/foo_spotify/backend/libspotify_backend.h new file mode 100644 index 0000000..5b9abae --- /dev/null +++ b/foo_spotify/backend/libspotify_backend.h @@ -0,0 +1,106 @@ +#pragma once + +#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, abort_callback& abort ); + void LogoutAndForget( abort_callback& abort ); + + std::string GetLoggedInUserName(); + +private: + void EventLoopThread(); + void StartEventLoopThread(); + void StopEventLoopThread(); + + std::optional WaitForLoginStatusUpdate( abort_callback& abort ); + + // 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(); + +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_; + + AudioBuffer audioBuffer_; +}; + +} // namespace sptf diff --git a/foo_spotify/backend/libspotify_backend_user.h b/foo_spotify/backend/libspotify_backend_user.h new file mode 100644 index 0000000..5797573 --- /dev/null +++ b/foo_spotify/backend/libspotify_backend_user.h @@ -0,0 +1,13 @@ +#pragma once + +namespace sptf +{ + +class LibSpotify_BackendUser +{ +public: + ~LibSpotify_BackendUser() = default; + virtual void Finalize() = 0; +}; + +} // namespace sptf diff --git a/foo_spotify/backend/libspotify_key.h b/foo_spotify/backend/libspotify_key.h new file mode 100644 index 0000000..9ea3b48 --- /dev/null +++ b/foo_spotify/backend/libspotify_key.h @@ -0,0 +1,31 @@ +#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 diff --git a/foo_spotify/backend/libspotify_wrapper.h b/foo_spotify/backend/libspotify_wrapper.h new file mode 100644 index 0000000..9e27bd0 --- /dev/null +++ b/foo_spotify/backend/libspotify_wrapper.h @@ -0,0 +1,136 @@ +#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 diff --git a/foo_spotify/backend/spotify_instance.cpp b/foo_spotify/backend/spotify_instance.cpp new file mode 100644 index 0000000..5368ec8 --- /dev/null +++ b/foo_spotify/backend/spotify_instance.cpp @@ -0,0 +1,100 @@ +#include + +#include "spotify_instance.h" + +#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_ ); +} + +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 ( !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 \ No newline at end of file diff --git a/foo_spotify/backend/spotify_instance.h b/foo_spotify/backend/spotify_instance.h new file mode 100644 index 0000000..3761172 --- /dev/null +++ b/foo_spotify/backend/spotify_instance.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace sptf +{ + +class AbortManager; +class LibSpotify_Backend; +class WebApi_Backend; + +namespace fb2k +{ + +class PlayCallbacks; + +} + +class SpotifyInstance +{ + +public: + ~SpotifyInstance() = default; + + static SpotifyInstance& Get(); + void Finalize(); + + 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 pAbortManager_; + std::unique_ptr pLibSpotify_backend_; + std::unique_ptr pWebApi_backend_; + bool fb2k_playCallbacks_initialized_ = false; +}; + +} // namespace sptf \ No newline at end of file diff --git a/foo_spotify/backend/spotify_object.cpp b/foo_spotify/backend/spotify_object.cpp new file mode 100644 index 0000000..4204de9 --- /dev/null +++ b/foo_spotify/backend/spotify_object.cpp @@ -0,0 +1,78 @@ +#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 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]; + qwr::QwrException::ExpectTrue( localType == "track"sv || localType == "album"sv || localType == "playlist"sv, "Unsupported Spotify object: {}", localType ); + type = std::string( localType.cbegin(), localType.cend() ); + + 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]; + qwr::QwrException::ExpectTrue( localType == "track"sv || localType == "album"sv || localType == "playlist"sv, "Unsupported Spotify object: {}", localType ); + type = std::string( localType.cbegin(), localType.cend() ); + + const auto localId = ret[2]; + qwr::QwrException::ExpectTrue( !localId.empty(), "Invalid Spotify object id: {}", localId ); + id = std::string( localId.cbegin(), localId.cend() ); + } +} + +std::string SpotifyObject::ToUri() +{ + return fmt::format( "spotify:{}:{}", type, id ); +} + +std::string SpotifyObject::ToUrl() +{ + return fmt::format( "https://open.spotify.com/{}/{}", type, id ); +} + +} // namespace sptf diff --git a/foo_spotify/backend/spotify_object.h b/foo_spotify/backend/spotify_object.h new file mode 100644 index 0000000..e50d722 --- /dev/null +++ b/foo_spotify/backend/spotify_object.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace sptf +{ + +struct SpotifyObject +{ + /// @throw qwr::QwrException + SpotifyObject( std::string_view input ); + std::string ToUri(); + std::string ToUrl(); + static bool IsValid( std::string_view input ); + + std::string type; + std::string id; +}; + +} // namespace sptf diff --git a/foo_spotify/backend/webapi_auth.cpp b/foo_spotify/backend/webapi_auth.cpp new file mode 100644 index 0000000..5604554 --- /dev/null +++ b/foo_spotify/backend/webapi_auth.cpp @@ -0,0 +1,483 @@ +#include + +#include "webapi_auth.h" + +#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 + +namespace fs = std::filesystem; + +namespace +{ + +constexpr wchar_t k_clientId[] = L"56b24aee069c4de2937c5e359de82b93"; + +} // namespace + +namespace +{ + +constexpr wchar_t codeVerifierChars[] = + L"abcdefghijklmnopqrstuvwxyz" + L"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + L"1234567890" + L"_.-~"; + +std::wstring GenerateCodeVerifier() +{ + 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 +{ + +WebApiAuthorizer::WebApiAuthorizer( AbortManager& abortManager ) + : abortManager_( abortManager ) + , expiresIn_( std::chrono::system_clock::now() ) +{ + 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 = web::json::value::parse( data ); + accessToken_ = dataJson.at( L"access_token" ).as_string(); + refreshToken_ = dataJson.at( L"refresh_token" ).as_string(); + expiresIn_ = std::chrono::time_point( std::chrono::minutes( dataJson.at( L"expires_in" ).as_integer() ) ); + } + catch ( const std::exception& ) + { + try + { + fs::remove( authpath ); + } + catch ( const fs::filesystem_error& ) + { + } + } + } + + // TODO: move to advanced + //config.set_proxy( web::web_proxy( L"http://127.0.0.1:3128" ) ); +} + +WebApiAuthorizer::~WebApiAuthorizer() +{ + cts_.cancel(); + StopResponseListener(); +} + +bool WebApiAuthorizer::IsAuthenticated() const +{ + std::lock_guard lock( accessTokenMutex_ ); + return !accessToken_.empty() && !refreshToken_.empty(); +} + +const std::wstring WebApiAuthorizer::GetAccessToken( abort_callback& abort ) +{ + std::lock_guard lock( accessTokenMutex_ ); + + if ( refreshToken_.empty() ) + { + ::fb2k::inMainThread( [&] { + playback_control::get()->stop(); + ui::NotAuthHandler::Get().ShowDialog(); + } ); + throw qwr::QwrException( "Failed to get authenticated Spotify session" ); + } + + if ( std::chrono::system_clock::now() - expiresIn_ > std::chrono::minutes( 1 ) ) + { + AuthenticateWithRefreshToken( abort ); + } + + assert( !accessToken_.empty() ); + return accessToken_; +} + +void WebApiAuthorizer::ClearAuth() +{ + codeVerifier_.clear(); + accessToken_.clear(); + refreshToken_.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( std::function onResponseEnd ) +{ + assert( core_api::is_main_thread() ); + + if ( pListener_ ) + { + return; + } + + StartResponseListener( onResponseEnd ); + + // + + web::uri_builder builder( url::accountsAuthenticate ); + builder.append_query( L"client_id", web::uri::encode_data_string( k_clientId ), false ); + builder.append_query( L"response_type", L"code" ); + builder.append_query( L"redirect_uri", web::uri::encode_data_string( url::redirectUri ), false ); + builder.append_query( L"code_challenge_method", L"S256" ); + codeVerifier_ = GenerateCodeVerifier(); + const auto challengeCode = GenerateChallengeCode( codeVerifier_ ); + builder.append_query( L"code_challenge", challengeCode ); + // TODO: cookie? + // builder.append_query( L"state", L"azaza" ); + builder.append_query( L"scope", web::uri::encode_data_string( L"playlist-read-collaborative playlist-read-private" ), false ); + + OpenAuthConfirmationInBrowser( builder.to_string() ); +} + +void WebApiAuthorizer::AuthenticateClean_Cleanup() +{ + StopResponseListener(); +} + +void WebApiAuthorizer::AuthenticateWithRefreshToken( abort_callback& abort ) +{ + web::uri_builder builder; + builder.append_query( L"grant_type", L"refresh_token" ); + assert( !refreshToken_.empty() ); + builder.append_query( L"refresh_token", refreshToken_ ); + builder.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 ); + + web::http::client::http_client client( url::accountsApi ); + auto response = client.request( req, cts_.get_token() ); + HandleAuthenticationResponse( response.get() ); +} + +pplx::task WebApiAuthorizer::CompleteAuthentication( const std::wstring& responseUrl ) +{ + // TODO: final_action + console::info( "foo_scrobble: Requesting session key" ); + + 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 ); + builder.append_query( L"grant_type", L"authorization_code" ); + builder.append_query( L"code", data.at( L"code" ) ); + builder.append_query( L"redirect_uri", web::uri::encode_data_string( url::redirectUri ), false ); + builder.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() ); + + web::http::client::http_client client( url::accountsApi ); + 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 ) + { + 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( qwr::unicode::ToU8( + fmt::format( L"{}: {}\n" + L"Additional data: {}\n", + response.status_code(), + response.reason_phrase(), + [&] { + try + { + return response.extract_json().get().serialize(); + } + 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() ); + + accessToken_ = responseJson.at( L"access_token" ).as_string(); + refreshToken_ = responseJson.at( L"refresh_token" ).as_string(); + expiresIn_ = std::chrono::system_clock::now() + std::chrono::seconds( responseJson.at( L"expires_in" ).as_integer() ); + + web::json::value configJson; + configJson[L"access_token"] = web::json::value( accessToken_ ); + configJson[L"refresh_token"] = web::json::value( refreshToken_ ); + configJson[L"expires_in"] = web::json::value( std::chrono::time_point_cast( expiresIn_ ).time_since_epoch().count() ); + + 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", qwr::unicode::ToU8( configJson.serialize() ) ); +} + +} // namespace sptf diff --git a/foo_spotify/backend/webapi_auth.h b/foo_spotify/backend/webapi_auth.h new file mode 100644 index 0000000..9992f3d --- /dev/null +++ b/foo_spotify/backend/webapi_auth.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include + +namespace web::http::experimental::listener +{ +class http_listener; +} + +namespace sptf +{ + +class AbortManager; + +class WebApiAuthorizer +{ +public: + WebApiAuthorizer( AbortManager& abortManager ); + ~WebApiAuthorizer(); + + bool IsAuthenticated() const; + + const std::wstring GetAccessToken( abort_callback& abort ); + + void ClearAuth(); + void CancelAuth(); + + void AuthenticateClean( std::function onResponseEnd ); + void AuthenticateClean_Cleanup(); + void AuthenticateWithRefreshToken( abort_callback& abort ); + +private: + 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_; + + std::unique_ptr pListener_; + + std::wstring codeVerifier_; + std::wstring state_; + + mutable std::mutex accessTokenMutex_; + std::wstring accessToken_; + std::wstring refreshToken_; + std::chrono::time_point expiresIn_; +}; + +} // namespace sptf \ No newline at end of file diff --git a/foo_spotify/backend/webapi_backend.cpp b/foo_spotify/backend/webapi_backend.cpp new file mode 100644 index 0000000..e0e9b9e --- /dev/null +++ b/foo_spotify/backend/webapi_backend.cpp @@ -0,0 +1,270 @@ +#include + +#include "webapi_backend.h" + +#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 sptf +{ + +WebApi_Backend::WebApi_Backend( AbortManager& abortManager ) + : abortManager_( abortManager ) + , client_( url::spotifyApi ) + , trackCache_( "tracks" ) + , artistCache_( "artists" ) + , albumImageCache_( "albums" ) + , artistImageCache_( "artists" ) + , pAuth_( std::make_unique( 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 ) +{ + web::uri_builder builder; + builder.append_path( L"me" ); + + const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); + return responseJson.get>(); +} + +std::unique_ptr +WebApi_Backend::GetTrack( const std::string& trackId, abort_callback& abort ) +{ + if ( auto trackOpt = trackCache_.GetObjectFromCache( trackId ); + trackOpt ) + { + return std::unique_ptr( std::move( *trackOpt ) ); + } + else + { + web::uri_builder builder; + builder.append_path( L"tracks" ); + builder.append_path( qwr::unicode::ToWide( trackId ) ); + + const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); + auto ret = responseJson.get>(); + + trackCache_.CacheObject( *ret ); + return std::unique_ptr( std::move( ret ) ); + } +} + +std::vector> +WebApi_Backend::GetTracksFromPlaylist( const std::string& playlistId, abort_callback& abort ) +{ + size_t offset = 0; + + std::vector> ret; + while ( true ) + { + web::uri_builder builder; + builder.append_path( fmt::format( L"playlists/{}/tracks", qwr::unicode::ToWide( playlistId ) ) ); + builder.append_query( L"limit", 100, false ); + builder.append_query( L"offset", offset, false ); + offset += 100; + + const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); + + const auto itemsIt = responseJson.find( "items" ); + qwr::QwrException::ExpectTrue( responseJson.cend() != itemsIt, + L"Malformed track data response response: missing `items`" ); + + auto playlistTracks = itemsIt->get>>(); + auto newData = playlistTracks | ranges::views::transform( []( auto& elem ) { return std::move( elem->track ); } ) | ranges::to_vector; + ret.insert( ret.end(), make_move_iterator( newData.begin() ), make_move_iterator( newData.end() ) ); + + if ( responseJson.at( "next" ).is_null() ) + { + break; + } + } + + trackCache_.CacheObjects( ret ); + return ret; +} + +std::vector> +WebApi_Backend::GetTracksFromAlbum( const std::string& albumId, abort_callback& abort ) +{ + auto album = [&] { + web::uri_builder builder; + builder.append_path( fmt::format( L"albums/{}", qwr::unicode::ToWide( albumId ) ) ); + + const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); + return responseJson.get>(); + }(); + + size_t offset = 0; + + std::vector> ret; + while ( true ) + { + web::uri_builder builder; + builder.append_path( fmt::format( L"albums/{}/tracks", qwr::unicode::ToWide( albumId ) ) ); + builder.append_query( L"limit", 50, false ); + builder.append_query( L"offset", offset, false ); + offset += 50; + + const auto responseJson = GetJsonResponse( builder.to_uri(), abort ); + + const auto itemsIt = responseJson.find( "items" ); + qwr::QwrException::ExpectTrue( responseJson.cend() != itemsIt, + L"Malformed track data response response: missing `items`" ); + + auto newData = itemsIt->get>>(); + ret.insert( ret.end(), make_move_iterator( newData.begin() ), make_move_iterator( newData.end() ) ); + + if ( responseJson.at( "next" ).is_null() ) + { + break; + } + } + + 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::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; +} + +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" ); + builder.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 ); +} + +nlohmann::json WebApi_Backend::GetJsonResponse( const web::uri& requestUri, abort_callback& abort ) +{ + web::http::http_request req( web::http::methods::GET ); + req.headers().add( L"Authorization", 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( requestUri ); + + auto ctsToken = cts_.get_token(); + auto localCts = Concurrency::cancellation_token_source::create_linked_source( ctsToken ); + const auto abortableScope = abortManager_.GetAbortableScope( [&localCts] { localCts.cancel(); }, abort ); + + const auto response = client_.request( req, localCts.get_token() ).get(); + if ( response.status_code() != 200 ) + { + throw qwr::QwrException( qwr::unicode::ToU8( + fmt::format( L"{}: {}\n" + L"Additional data: {}\n", + response.status_code(), + response.reason_phrase(), + [&] { + try + { + return response.extract_json().get().serialize(); + } + catch ( ... ) + { + return response.to_string(); + } + }() ) ) ); + } + + const auto responseJson = nlohmann::json::parse( response.extract_string().get() ); + qwr::QwrException::ExpectTrue( responseJson.is_object(), + L"Malformed track data response response: json is not an object" ); + + return responseJson; +} + +} // namespace sptf diff --git a/foo_spotify/backend/webapi_backend.h b/foo_spotify/backend/webapi_backend.h new file mode 100644 index 0000000..0094cdc --- /dev/null +++ b/foo_spotify/backend/webapi_backend.h @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include +#include + +#include +#include +#include + +namespace sptf +{ + +struct WebApi_User; +struct WebApi_Track; +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 ); + + std::unique_ptr + GetTrack( const std::string& trackId, abort_callback& abort ); + + std::vector> + GetTracksFromPlaylist( const std::string& playlistId, abort_callback& abort ); + + std::vector> + GetTracksFromAlbum( const std::string& albumId, abort_callback& abort ); + + std::vector> + GetMetaForTracks( nonstd::span> tracks ); + + 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: + nlohmann::json GetJsonResponse( const web::uri& requestUri, abort_callback& abort ); + +private: + AbortManager& abortManager_; + + pplx::cancellation_token_source cts_; + std::unique_ptr pAuth_; + web::http::client::http_client client_; + + WebApi_ObjectCache trackCache_; + WebApi_ObjectCache artistCache_; + + WebApi_ImageCache albumImageCache_; + WebApi_ImageCache artistImageCache_; +}; + +} // namespace sptf \ No newline at end of file diff --git a/foo_spotify/backend/webapi_cache.cpp b/foo_spotify/backend/webapi_cache.cpp new file mode 100644 index 0000000..47217e6 --- /dev/null +++ b/foo_spotify/backend/webapi_cache.cpp @@ -0,0 +1,157 @@ +#include + +#include "webapi_cache.h" + +#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_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 ); + qwr::error::CheckHR( hr, "URLDownloadToFile" ); + } + assert( fs::exists( imagePath ) ); + return imagePath; +} + +} // namespace sptf \ No newline at end of file diff --git a/foo_spotify/backend/webapi_cache.h b/foo_spotify/backend/webapi_cache.h new file mode 100644 index 0000000..261610c --- /dev/null +++ b/foo_spotify/backend/webapi_cache.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace sptf +{ + +template +class WebApi_ObjectCache +{ +public: + WebApi_ObjectCache( const std::string& cacheSubdir ) + : cacheSubdir_( cacheSubdir ) + { + } + + void CacheObject( const T& object, bool force = false ) + { + std::lock_guard lock( cacheMutex_ ); + CacheObjectNonBlocking( object, force ); + } + + void CacheObjects( const nonstd::span> objects, bool force = false ) + { + std::lock_guard lock( cacheMutex_ ); + for ( const auto& pObject: objects ) + { + CacheObjectNonBlocking( *pObject, force ); + } + } + + void CacheObjects( const nonstd::span> objects, bool force = false ) + { + std::lock_guard lock( cacheMutex_ ); + for ( const auto& pObject: objects ) + { + CacheObjectNonBlocking( *pObject, force ); + } + } + + std::optional> + GetObjectFromCache( const std::string& id ) + { + namespace fs = std::filesystem; + + std::lock_guard lock( cacheMutex_ ); + + const auto filePath = GetCachedPath( id ); + 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; + } + } + +private: + void CacheObjectNonBlocking( const T& object, bool force ) + { + namespace fs = std::filesystem; + + const auto filePath = GetCachedPath( object.id ); + 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 ) ); + } + + std::filesystem::path GetCachedPath( const std::string& id ) const + { + return path::WebApiCache() / "data" / cacheSubdir_ / fmt::format( "{}.json", id ); + } + +private: + std::string cacheSubdir_; + std::mutex cacheMutex_; +}; + +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 \ No newline at end of file diff --git a/foo_spotify/backend/webapi_objects/webapi_album.cpp b/foo_spotify/backend/webapi_objects/webapi_album.cpp new file mode 100644 index 0000000..05148ea --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_album.cpp @@ -0,0 +1,13 @@ +#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 diff --git a/foo_spotify/backend/webapi_objects/webapi_album.h b/foo_spotify/backend/webapi_objects/webapi_album.h new file mode 100644 index 0000000..a606dca --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_album.h @@ -0,0 +1,32 @@ +#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 artist’s 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 \ No newline at end of file diff --git a/foo_spotify/backend/webapi_objects/webapi_artist.cpp b/foo_spotify/backend/webapi_objects/webapi_artist.cpp new file mode 100644 index 0000000..49d6a70 --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_artist.cpp @@ -0,0 +1,14 @@ +#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 diff --git a/foo_spotify/backend/webapi_objects/webapi_artist.h b/foo_spotify/backend/webapi_objects/webapi_artist.h new file mode 100644 index 0000000..69189d7 --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_artist.h @@ -0,0 +1,36 @@ +#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 \ No newline at end of file diff --git a/foo_spotify/backend/webapi_objects/webapi_image.cpp b/foo_spotify/backend/webapi_objects/webapi_image.cpp new file mode 100644 index 0000000..0b4a83d --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_image.cpp @@ -0,0 +1,10 @@ +#include + +#include "webapi_image.h" + +namespace sptf +{ + +SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Image, height, url, width ); + +} // namespace sptf diff --git a/foo_spotify/backend/webapi_objects/webapi_image.h b/foo_spotify/backend/webapi_objects/webapi_image.h new file mode 100644 index 0000000..cfe59f3 --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_image.h @@ -0,0 +1,19 @@ +#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 \ No newline at end of file diff --git a/foo_spotify/backend/webapi_objects/webapi_objects_all.h b/foo_spotify/backend/webapi_objects/webapi_objects_all.h new file mode 100644 index 0000000..1df4457 --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_objects_all.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include +#include +#include diff --git a/foo_spotify/backend/webapi_objects/webapi_playlist_track.cpp b/foo_spotify/backend/webapi_objects/webapi_playlist_track.cpp new file mode 100644 index 0000000..74b9745 --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_playlist_track.cpp @@ -0,0 +1,13 @@ +#include + +#include "webapi_playlist_track.h" + +#include +#include + +namespace sptf +{ + +SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_PlaylistTrack, track ); + +} // namespace sptf diff --git a/foo_spotify/backend/webapi_objects/webapi_playlist_track.h b/foo_spotify/backend/webapi_objects/webapi_playlist_track.h new file mode 100644 index 0000000..5f091d6 --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_playlist_track.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +namespace sptf +{ + +struct WebApi_Track; + +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. + // is_local a Boolean Whether this track is a local file or not. + std::unique_ptr track; +}; + +void to_json( nlohmann::json& j, const WebApi_PlaylistTrack& p ); +void from_json( const nlohmann::json& j, WebApi_PlaylistTrack& p ); + +} // namespace sptf diff --git a/foo_spotify/backend/webapi_objects/webapi_track.cpp b/foo_spotify/backend/webapi_objects/webapi_track.cpp new file mode 100644 index 0000000..de3fc89 --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_track.cpp @@ -0,0 +1,27 @@ +#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; + preview_url = trackSimplified->preview_url; + track_number = trackSimplified->track_number; + id = trackSimplified->id; +} + +SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Track_Simplified, artists, disc_number, duration_ms, name, preview_url, track_number, id ); +SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Track, album, artists, disc_number, duration_ms, name, preview_url, track_number, id ); + +} // namespace sptf diff --git a/foo_spotify/backend/webapi_objects/webapi_track.h b/foo_spotify/backend/webapi_objects/webapi_track.h new file mode 100644 index 0000000..a1a0b75 --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_track.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include + +namespace sptf +{ + +struct WebApi_Album_Simplified; +struct WebApi_Artist_Simplified; + +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. + // linked_from a linked track object Part of the response when Track Relinking is applied and is only part of the response if the track linking, in fact, exists. The requested track has been replaced with a different track. The track in the linked_from object contains information about the originally requested track. + // 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"} + // 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::string name; + std::string 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. + //linked_from a linked track object Part of the response when Track Relinking is applied, and the requested track has been replaced with different track. The track in the linked_from object contains information about the originally requested track. + //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"} + //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::string name; + std::string 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 diff --git a/foo_spotify/backend/webapi_objects/webapi_user.cpp b/foo_spotify/backend/webapi_objects/webapi_user.cpp new file mode 100644 index 0000000..ca914f5 --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_user.cpp @@ -0,0 +1,12 @@ +#include + +#include "webapi_user.h" + +#include + +namespace sptf +{ + +SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_User, display_name, uri ); + +} // namespace sptf diff --git a/foo_spotify/backend/webapi_objects/webapi_user.h b/foo_spotify/backend/webapi_objects/webapi_user.h new file mode 100644 index 0000000..512defb --- /dev/null +++ b/foo_spotify/backend/webapi_objects/webapi_user.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace sptf +{ + +struct WebApi_User +{ + //country string The country of the user, as set in the user’s account profile. An ISO 3166-1 alpha-2 country code. This field is only available when the current user has granted access to the user-read-private scope. + 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. + //id string The Spotify user ID for the user. + //images an array of image objects The user’s profile image. + //product string The user’s 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 diff --git a/foo_spotify/component_defines.h b/foo_spotify/component_defines.h new file mode 100644 index 0000000..a1c4c9c --- /dev/null +++ b/foo_spotify/component_defines.h @@ -0,0 +1,39 @@ +#pragma once + +// Generated by scripts from setup.bat +#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 ) + +#define SPTF_VERSION_MAJOR 0 +#define SPTF_VERSION_MINOR 1 +#define SPTF_VERSION_PATCH 0 +#define SPTF_VERSION_PRERELEASE_TEXT "beta" + +#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" diff --git a/foo_spotify/component_guids.h b/foo_spotify/component_guids.h new file mode 100644 index 0000000..ef31050 --- /dev/null +++ b/foo_spotify/component_guids.h @@ -0,0 +1,13 @@ +#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_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 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 diff --git a/foo_spotify/component_paths.cpp b/foo_spotify/component_paths.cpp new file mode 100644 index 0000000..f01ec78 --- /dev/null +++ b/foo_spotify/component_paths.cpp @@ -0,0 +1,30 @@ +#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 \ No newline at end of file diff --git a/foo_spotify/component_paths.h b/foo_spotify/component_paths.h new file mode 100644 index 0000000..79150d0 --- /dev/null +++ b/foo_spotify/component_paths.h @@ -0,0 +1,14 @@ +#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 diff --git a/foo_spotify/component_urls.h b/foo_spotify/component_urls.h new file mode 100644 index 0000000..2e29a88 --- /dev/null +++ b/foo_spotify/component_urls.h @@ -0,0 +1,14 @@ +#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:23017"; + +constexpr wchar_t homepage[] = L"https://github.com/TheQwertiest/" SPTF_UNDERSCORE_NAME; + +} // namespace sptf::guid diff --git a/foo_spotify/dllmain.cpp b/foo_spotify/dllmain.cpp new file mode 100644 index 0000000..0edb246 --- /dev/null +++ b/foo_spotify/dllmain.cpp @@ -0,0 +1,85 @@ +#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( + "!!! `foo_input_spotify` component detected! !!!\n" + "!!! It is incompatible with `foo_spotify` and must be removed! !!!\n" + "!!! Otherwise foobar2000 *will* crash! !!!", + SPTF_UNDERSCORE_NAME ); + } + } + + 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; +} diff --git a/foo_spotify/fb2k/acfu_integration.cpp b/foo_spotify/fb2k/acfu_integration.cpp new file mode 100644 index 0000000..8b35cb9 --- /dev/null +++ b/foo_spotify/fb2k/acfu_integration.cpp @@ -0,0 +1,66 @@ +#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 diff --git a/foo_spotify/fb2k/advanced_config.cpp b/foo_spotify/fb2k/advanced_config.cpp new file mode 100644 index 0000000..cd8783b --- /dev/null +++ b/foo_spotify/fb2k/advanced_config.cpp @@ -0,0 +1,23 @@ +#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 ); + +} // namespace + +namespace sptf::config::advanced +{ + +advconfig_string_factory network_proxy( + "Proxy", + sptf::guid::adv_var_network_proxy, sptf::guid::adv_branch_network, 0, + "" ); + +} // namespace sptf::config::advanced diff --git a/foo_spotify/fb2k/advanced_config.h b/foo_spotify/fb2k/advanced_config.h new file mode 100644 index 0000000..be78340 --- /dev/null +++ b/foo_spotify/fb2k/advanced_config.h @@ -0,0 +1,8 @@ +#pragma once + +namespace sptf::config::advanced +{ + +extern advconfig_string_factory network_proxy; + +} // namespace sptf::config::advanced diff --git a/foo_spotify/fb2k/album_art.cpp b/foo_spotify/fb2k/album_art.cpp new file mode 100644 index 0000000..647a752 --- /dev/null +++ b/foo_spotify/fb2k/album_art.cpp @@ -0,0 +1,126 @@ +#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(); + + 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 ); +} + +AlbumArtExtractorSpotify::AlbumArtExtractorSpotify() +{ +} + +bool AlbumArtExtractorSpotify::is_our_path( const char* p_path, const char* p_extension ) +{ + const auto spotifyObject = [&]() -> std::optional { + try + { + return SpotifyObject( p_path ); + } + catch ( const qwr::QwrException& ) + { + return std::nullopt; + } + }(); + + return ( spotifyObject && spotifyObject->type == "track" ); +} + +album_art_extractor_instance_ptr AlbumArtExtractorSpotify::open( file_ptr p_filehint, const char* p_path, abort_callback& p_abort ) +{ + SpotifyObject object( p_path ); + assert( object.type == "track" ); + + auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend(); + auto track = waBackend.GetTrack( object.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; +} diff --git a/foo_spotify/fb2k/file_info_filler.cpp b/foo_spotify/fb2k/file_info_filler.cpp new file mode 100644 index 0000000..57612d4 --- /dev/null +++ b/foo_spotify/fb2k/file_info_filler.cpp @@ -0,0 +1,61 @@ +#include + +#include "file_info_filler.h" + +#include + +namespace sptf::fb2k +{ + +void FillFileInfoWithMeta( const std::unordered_multimap& meta, file_info& info ) +{ + auto addMeta = [&]( file_info& p_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; + p_info.meta_add( metaName.data(), val.c_str() ); + } + }; + auto addMetaIfPositive = [&]( file_info& p_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 ) + { + p_info.meta_add( metaName.data(), val.c_str() ); + } + } + }; + auto addMetaMultiple = [&]( file_info& p_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] = *er.first; + p_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 ); + } + } +} + +} // namespace sptf::fb2k diff --git a/foo_spotify/fb2k/file_info_filler.h b/foo_spotify/fb2k/file_info_filler.h new file mode 100644 index 0000000..5003445 --- /dev/null +++ b/foo_spotify/fb2k/file_info_filler.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace sptf::fb2k +{ + +void FillFileInfoWithMeta( const std::unordered_multimap& meta, file_info& info ); + +} \ No newline at end of file diff --git a/foo_spotify/fb2k/input.cpp b/foo_spotify/fb2k/input.cpp new file mode 100644 index 0000000..6903194 --- /dev/null +++ b/foo_spotify/fb2k/input.cpp @@ -0,0 +1,407 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +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::unordered_multimap trackMeta_; + + t_filestats stats_; + int channels_; + int sampleRate_; +}; +} // 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(); + + SpotifyObject so( p_path ); + const auto track = waBackend.GetTrack( so.id, p_abort ); + trackMeta_ = waBackend.GetMetaForTracks( nonstd::span>( &track, 1 ) )[0]; + + if ( p_reason == input_open_info_read ) + { // don't use LibSpotify stuff if not required + return; + } + + auto& lsBackend = GetInitializedLibSpotify(); + + auto pSession = lsBackend.GetInitializedSpSession( p_abort ); + + lsBackend.ExecSpMutex( [&] { + wrapper::Ptr link( sp_link_create_from_string( so.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 ); + } +} + +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 stats_; +} + +void InputSpotify::decode_initialize( t_int32 subsong, unsigned p_flags, abort_callback& p_abort ) +{ + 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: {}", sp_error_message( sp ) ) ); + } + + 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 ) +{ + auto& lsBackend = GetInitializedLibSpotify(); + lsBackend.GetAudioBuffer().clear(); // TODO: remove? + 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 ) +{ + p_out.info_set_int( "CHANNELS", channels_ ); + p_out.info_set_int( "SAMPLERATE", sampleRate_ ); + 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 ) +{ // accept only filtered input + return std::string_view( p_full_path )._Starts_with( "spotify:track:" ); +} + +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 diff --git a/foo_spotify/fb2k/playback.cpp b/foo_spotify/fb2k/playback.cpp new file mode 100644 index 0000000..81c2686 --- /dev/null +++ b/foo_spotify/fb2k/playback.cpp @@ -0,0 +1,80 @@ +#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; + +} diff --git a/foo_spotify/fb2k/playback.h b/foo_spotify/fb2k/playback.h new file mode 100644 index 0000000..5c58980 --- /dev/null +++ b/foo_spotify/fb2k/playback.h @@ -0,0 +1,44 @@ +#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 diff --git a/foo_spotify/fb2k/playlist_loader.cpp b/foo_spotify/fb2k/playlist_loader.cpp new file mode 100644 index 0000000..dd37da4 --- /dev/null +++ b/foo_spotify/fb2k/playlist_loader.cpp @@ -0,0 +1,172 @@ +#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 +{ + +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(); + + const auto tracks = [&] { + if ( spotifyObject.type == "album" ) + { + return waBackend.GetTracksFromAlbum( spotifyObject.id, p_abort ); + } + else if ( spotifyObject.type == "playlist" ) + { + return waBackend.GetTracksFromPlaylist( spotifyObject.id, p_abort ); + } + else + { + std::vector> tmp; + tmp.emplace_back( waBackend.GetTrack( spotifyObject.id, p_abort ) ); + return tmp; + } + }(); + + 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( fmt::format( "spotify:track:{}", track->id ).c_str(), 0 ) ); + p_callback->on_entry_info( f_handle, playlist_loader_callback::entry_user_requested, filestats_invalid, f_info, false ); + } +} + +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 "spotify"; +} + +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; + return ( mime == p_content_type ); +} + +bool PlaylistLoaderSpotify::is_associatable() +{ + return false; +} + +} // namespace + +namespace +{ + +playlist_loader_factory_t g_playlist_loader; + +} // namespace diff --git a/foo_spotify/foo_spotify.rc b/foo_spotify/foo_spotify.rc new file mode 100644 index 0000000..374c990 --- /dev/null +++ b/foo_spotify/foo_spotify.rc @@ -0,0 +1,139 @@ +// 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_PREFERENCES 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 + GROUPBOX "Authentication",IDC_STATIC,5,18,295,113 + PUSHBUTTON "LS Login",IDC_BTN_LOGIN_LIBSPOTIFY,17,48,50,14 + PUSHBUTTON "WA Login",IDC_BTN_LOGIN_WEBAPI,17,96,50,14 + LTEXT "LS status:",IDC_STATIC_LIBSPOTIFY_STATUS,72,50,217,19 + LTEXT "WA status:",IDC_STATIC_WEBAPI_STATUS,72,98,217,19 + GROUPBOX "LibSpotify",IDC_STATIC,11,33,282,40 + GROUPBOX "WebAPI",IDC_STATIC,11,81,282,40 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_NOT_AUTH, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 242 + TOPMARGIN, 7 + BOTTOMMARGIN, 70 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_DIALOG_NOT_AUTH AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PREFERENCES AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_NOT_AUTH 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 + diff --git a/foo_spotify/foo_spotify.rc2 b/foo_spotify/foo_spotify.rc2 new file mode 100644 index 0000000..b2cfd43 --- /dev/null +++ b/foo_spotify/foo_spotify.rc2 @@ -0,0 +1,60 @@ +// +// 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) 2018-2019 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 diff --git a/foo_spotify/foo_spotify.vcxproj b/foo_spotify/foo_spotify.vcxproj new file mode 100644 index 0000000..ff97c38 --- /dev/null +++ b/foo_spotify/foo_spotify.vcxproj @@ -0,0 +1,208 @@ + + + + + + + + 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}. + + + + + + + + + \ No newline at end of file diff --git a/foo_spotify/foo_spotify.vcxproj.filters b/foo_spotify/foo_spotify.vcxproj.filters new file mode 100644 index 0000000..c236a2d --- /dev/null +++ b/foo_spotify/foo_spotify.vcxproj.filters @@ -0,0 +1,202 @@ + + + + + + + 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 + + + ui + + + backend\webapi_objects + + + backend + + + ui + + + + fb2k + + + + + + + + utils + + + backend + + + backend + + + backend + + + utils + + + backend + + + backend + + + backend + + + utils + + + + + backend + + + backend + + + backend\webapi_objects + + + backend\webapi_objects + + + backend\webapi_objects + + + backend\webapi_objects + + + backend\webapi_objects + + + utils + + + utils + + + fb2k + + + backend + + + fb2k + + + utils + + + resources + + + backend\webapi_objects + + + ui + + + backend\webapi_objects + + + backend + + + fb2k + + + ui + + + + + {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 + + + \ No newline at end of file diff --git a/foo_spotify/packages.config b/foo_spotify/packages.config new file mode 100644 index 0000000..51b6626 --- /dev/null +++ b/foo_spotify/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/foo_spotify/resource.h b/foo_spotify/resource.h new file mode 100644 index 0000000..1f20ae4 --- /dev/null +++ b/foo_spotify/resource.h @@ -0,0 +1,24 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by foo_spotify.rc +// +#define VS_VERSION_INFO 1 +#define IDD_PREFERENCES 101 +#define IDD_NOT_AUTH 102 +#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 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +# ifndef APSTUDIO_READONLY_SYMBOLS +# define _APS_NEXT_RESOURCE_VALUE 103 +# define _APS_NEXT_COMMAND_VALUE 40001 +# define _APS_NEXT_CONTROL_VALUE 1009 +# define _APS_NEXT_SYMED_VALUE 101 +# endif +#endif diff --git a/foo_spotify/stdafx.cpp b/foo_spotify/stdafx.cpp new file mode 100644 index 0000000..c666761 --- /dev/null +++ b/foo_spotify/stdafx.cpp @@ -0,0 +1 @@ +#include diff --git a/foo_spotify/stdafx.h b/foo_spotify/stdafx.h new file mode 100644 index 0000000..affdce8 --- /dev/null +++ b/foo_spotify/stdafx.h @@ -0,0 +1,70 @@ +#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 diff --git a/foo_spotify/ui/ui_not_auth.cpp b/foo_spotify/ui/ui_not_auth.cpp new file mode 100644 index 0000000..71a1f94 --- /dev/null +++ b/foo_spotify/ui/ui_not_auth.cpp @@ -0,0 +1,119 @@ +#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 \ No newline at end of file diff --git a/foo_spotify/ui/ui_not_auth.h b/foo_spotify/ui/ui_not_auth.h new file mode 100644 index 0000000..5a71df2 --- /dev/null +++ b/foo_spotify/ui/ui_not_auth.h @@ -0,0 +1,63 @@ +#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 diff --git a/foo_spotify/ui/ui_preferences.cpp b/foo_spotify/ui/ui_preferences.cpp new file mode 100644 index 0000000..9db6148 --- /dev/null +++ b/foo_spotify/ui/ui_preferences.cpp @@ -0,0 +1,345 @@ +#include + +#include "ui_preferences.h" + +#include +#include +#include +#include +#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( 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 +{ + +Preferences::Preferences( preferences_page_callback::ptr callback ) +{ + (void)callback; +} + +Preferences::~Preferences() +{ +} + +HWND Preferences::get_wnd() +{ + return m_hWnd; +} + +t_uint32 Preferences::get_state() +{ + return 0; +} + +void Preferences::apply() +{ + return; +} + +void Preferences::reset() +{ + return; +} + +BOOL Preferences::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 ); + + pStatusThread_ = std::make_unique( [this] { + qwr::TimedAbortCallback tac; + + const auto lsStatus = SpotifyInstance::Get().GetLibSpotify_Backend().Relogin( tac ); + const auto waStatus = [&] { + auto& auth = SpotifyInstance::Get().GetWebApi_Backend().GetAuthorizer(); + if ( !auth.IsAuthenticated() ) + { + return false; + } + try + { + auth.AuthenticateWithRefreshToken( 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 Preferences::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 Preferences::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_BTNFACE ) ); + return (HBRUSH)GetStockObject( HOLLOW_BRUSH ); + } + + SetMsgHandled( FALSE ); + + return 0; +} + +void Preferences::OnLibSpotifyLoginClick( UINT uNotifyCode, int nID, CWindow wndCtl ) +{ + (void)uNotifyCode; + (void)nID; + (void)wndCtl; + + qwr::TimedAbortCallback tac; + + auto& lsBackend = SpotifyInstance::Get().GetLibSpotify_Backend(); + if ( libSpotifyStatus_ == LoginStatus::logged_out ) + { + libSpotifyStatus_ = ( lsBackend.LoginWithUI( m_hWnd, tac ) ? LoginStatus::logged_in : LoginStatus::logged_out ); + } + else + { + lsBackend.LogoutAndForget( tac ); + libSpotifyStatus_ = LoginStatus::logged_out; + } + + UpdateLibSpotifyUi(); +} + +void Preferences::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 + { + auth.AuthenticateClean( [&] { ::PostMessage( m_hWnd, kOnWebApiLoginResponse, 0, 0 ); } ); + } + catch ( const std::exception& e ) + { + qwr::ReportErrorWithPopup( fmt::format( "WebAPI login failed:\n{}", e.what() ), SPTF_NAME ); + + webApiStatus_ = LoginStatus::logged_out; + UpdateWebApiUi(); + } + } + else + { + auth.ClearAuth(); + + webApiStatus_ = LoginStatus::logged_out; + UpdateWebApiUi(); + } +} + +LRESULT Preferences::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.IsAuthenticated() ? LoginStatus::logged_in : LoginStatus::logged_out ); + UpdateWebApiUi(); + + return 0; +} + +LRESULT Preferences::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 Preferences::UpdateLibSpotifyUi() +{ + const auto getUsername = [] { + return SpotifyInstance::Get().GetLibSpotify_Backend().GetLoggedInUserName(); + }; + UpdateBackendUi( libSpotifyStatus_, btnLibSpotify_, textLibSpotify_, getUsername ); +} + +void Preferences::UpdateWebApiUi() +{ + const auto getUsername = [] { + qwr::TimedAbortCallback tac; + 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 Preferences::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 \ No newline at end of file diff --git a/foo_spotify/ui/ui_preferences.h b/foo_spotify/ui/ui_preferences.h new file mode 100644 index 0000000..43e2ef0 --- /dev/null +++ b/foo_spotify/ui/ui_preferences.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include + +namespace sptf::ui +{ + +class Preferences + : public CDialogImpl + , public preferences_page_instance +{ +private: + enum class LoginStatus + { + fetching_login_status, + logged_out, + login_in_progress, + logged_in, + }; + +public: + enum + { + IDD = IDD_PREFERENCES + }; + + Preferences( preferences_page_callback::ptr callback ); + ~Preferences() override; + + // preferences_page_instance + HWND get_wnd() 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( Preferences ) + 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 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 UpdateLibSpotifyUi(); + void UpdateWebApiUi(); + void UpdateBackendUi( LoginStatus loginStatus, CButton& btn, CStatic& text, std::function getUserNameFn ); + +private: + preferences_page_callback::ptr callback_; + + 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 diff --git a/foo_spotify/utils/abort_manager.cpp b/foo_spotify/utils/abort_manager.cpp new file mode 100644 index 0000000..5fcced6 --- /dev/null +++ b/foo_spotify/utils/abort_manager.cpp @@ -0,0 +1,122 @@ +#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 handle = itTask->second.pAbort; + idToTask_.erase( itTask ); + + const auto itIds = abortToIds_.find( handle ); + 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 ); + } +} + +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_ ); + eventCv_.wait_for( lock, std::chrono::seconds( 2 ), [&] { return ( 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 diff --git a/foo_spotify/utils/abort_manager.h b/foo_spotify/utils/abort_manager.h new file mode 100644 index 0000000..f467329 --- /dev/null +++ b/foo_spotify/utils/abort_manager.h @@ -0,0 +1,110 @@ +#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_ ); + auto id = idCounter_; + while ( idToTask_.count( id ) ) + { + id = ++idCounter_; + } + return id; + }(); + + { + std::unique_lock lock( mutex_ ); + + 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 } ); + } + 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 diff --git a/foo_spotify/utils/async_mutex.hpp b/foo_spotify/utils/async_mutex.hpp new file mode 100644 index 0000000..dddb096 --- /dev/null +++ b/foo_spotify/utils/async_mutex.hpp @@ -0,0 +1,195 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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 diff --git a/foo_spotify/utils/cred_prompt.cpp b/foo_spotify/utils/cred_prompt.cpp new file mode 100644 index 0000000..50b1171 --- /dev/null +++ b/foo_spotify/utils/cred_prompt.cpp @@ -0,0 +1,79 @@ +#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 diff --git a/foo_spotify/utils/cred_prompt.h b/foo_spotify/utils/cred_prompt.h new file mode 100644 index 0000000..fed0e62 --- /dev/null +++ b/foo_spotify/utils/cred_prompt.h @@ -0,0 +1,17 @@ +#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 diff --git a/foo_spotify/utils/json_macro_fix.h b/foo_spotify/utils/json_macro_fix.h new file mode 100644 index 0000000..c59d95c --- /dev/null +++ b/foo_spotify/utils/json_macro_fix.h @@ -0,0 +1,12 @@ +#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__ ) ) \ + } diff --git a/foo_spotify/utils/json_std_extenders.h b/foo_spotify/utils/json_std_extenders.h new file mode 100644 index 0000000..5be61a5 --- /dev/null +++ b/foo_spotify/utils/json_std_extenders.h @@ -0,0 +1,81 @@ +#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(); + } + } +}; + +} // namespace nlohmann diff --git a/foo_spotify/utils/secure_vector.h b/foo_spotify/utils/secure_vector.h new file mode 100644 index 0000000..9073b62 --- /dev/null +++ b/foo_spotify/utils/secure_vector.h @@ -0,0 +1,63 @@ +#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 diff --git a/libspotify/ChangeLog b/libspotify/ChangeLog new file mode 100644 index 0000000..dde871f --- /dev/null +++ b/libspotify/ChangeLog @@ -0,0 +1,491 @@ +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 diff --git a/libspotify/LICENSE b/libspotify/LICENSE new file mode 100644 index 0000000..037cb0d --- /dev/null +++ b/libspotify/LICENSE @@ -0,0 +1,3 @@ +For the current terms and conditions, please read: + +http://developer.spotify.com/en/libspotify/terms-of-use/ diff --git a/libspotify/README b/libspotify/README new file mode 100644 index 0000000..54aac6c --- /dev/null +++ b/libspotify/README @@ -0,0 +1,71 @@ + + 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. diff --git a/libspotify/docs/html/annotated.html b/libspotify/docs/html/annotated.html new file mode 100644 index 0000000..55aa0bb --- /dev/null +++ b/libspotify/docs/html/annotated.html @@ -0,0 +1,48 @@ + + + + +libspotify: Data Structures + + + + + + +
+
+

Data Structures

+
+ +
+Generated on Wed Jun 13 2012 14:22:40.
+Copyright © 2006–2012 Spotify Ltd
+ + diff --git a/libspotify/docs/html/api_8h.html b/libspotify/docs/html/api_8h.html new file mode 100644 index 0000000..03bb941 --- /dev/null +++ b/libspotify/docs/html/api_8h.html @@ -0,0 +1,654 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/bc_s.png b/libspotify/docs/html/bc_s.png new file mode 100644 index 0000000..e401862 Binary files /dev/null and b/libspotify/docs/html/bc_s.png differ diff --git a/libspotify/docs/html/browse_8c-example.html b/libspotify/docs/html/browse_8c-example.html new file mode 100644 index 0000000..a50022d --- /dev/null +++ b/libspotify/docs/html/browse_8c-example.html @@ -0,0 +1,299 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/classes.html b/libspotify/docs/html/classes.html new file mode 100644 index 0000000..7a15f8e --- /dev/null +++ b/libspotify/docs/html/classes.html @@ -0,0 +1,42 @@ + + + + +libspotify: Alphabetical List + + + + + + +
+
+

Data Structure Index

+
+ +
+Generated on Wed Jun 13 2012 14:22:40.
+Copyright © 2006–2012 Spotify Ltd
+ + diff --git a/libspotify/docs/html/closed.png b/libspotify/docs/html/closed.png new file mode 100644 index 0000000..b7d4bd9 Binary files /dev/null and b/libspotify/docs/html/closed.png differ diff --git a/libspotify/docs/html/doxygen.css b/libspotify/docs/html/doxygen.css new file mode 100644 index 0000000..51b5407 --- /dev/null +++ b/libspotify/docs/html/doxygen.css @@ -0,0 +1,253 @@ +/* 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%; + } +} diff --git a/libspotify/docs/html/doxygen.png b/libspotify/docs/html/doxygen.png new file mode 100644 index 0000000..635ed52 Binary files /dev/null and b/libspotify/docs/html/doxygen.png differ diff --git a/libspotify/docs/html/examples.html b/libspotify/docs/html/examples.html new file mode 100644 index 0000000..ad89511 --- /dev/null +++ b/libspotify/docs/html/examples.html @@ -0,0 +1,42 @@ + + + + +libspotify: Examples + + + + + + +
+
+

Examples

+
+
+Here is a list of all examples: +
+
+Generated on Wed Jun 13 2012 14:22:40.
+Copyright © 2006–2012 Spotify Ltd
+ + diff --git a/libspotify/docs/html/files.html b/libspotify/docs/html/files.html new file mode 100644 index 0000000..bc7bf15 --- /dev/null +++ b/libspotify/docs/html/files.html @@ -0,0 +1,41 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/functions.html b/libspotify/docs/html/functions.html new file mode 100644 index 0000000..7988890 --- /dev/null +++ b/libspotify/docs/html/functions.html @@ -0,0 +1,328 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/functions_vars.html b/libspotify/docs/html/functions_vars.html new file mode 100644 index 0000000..4b21b7b --- /dev/null +++ b/libspotify/docs/html/functions_vars.html @@ -0,0 +1,328 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/globals.html b/libspotify/docs/html/globals.html new file mode 100644 index 0000000..5c762fd --- /dev/null +++ b/libspotify/docs/html/globals.html @@ -0,0 +1,62 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/globals_0x69.html b/libspotify/docs/html/globals_0x69.html new file mode 100644 index 0000000..df73b21 --- /dev/null +++ b/libspotify/docs/html/globals_0x69.html @@ -0,0 +1,62 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/globals_0x73.html b/libspotify/docs/html/globals_0x73.html new file mode 100644 index 0000000..4d82dc8 --- /dev/null +++ b/libspotify/docs/html/globals_0x73.html @@ -0,0 +1,1214 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/globals_0x74.html b/libspotify/docs/html/globals_0x74.html new file mode 100644 index 0000000..a81d4f3 --- /dev/null +++ b/libspotify/docs/html/globals_0x74.html @@ -0,0 +1,59 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/globals_defs.html b/libspotify/docs/html/globals_defs.html new file mode 100644 index 0000000..8e85f80 --- /dev/null +++ b/libspotify/docs/html/globals_defs.html @@ -0,0 +1,52 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/globals_enum.html b/libspotify/docs/html/globals_enum.html new file mode 100644 index 0000000..22ab238 --- /dev/null +++ b/libspotify/docs/html/globals_enum.html @@ -0,0 +1,103 @@ + + + + +libspotify: Data Fields + + + + + + +
    +
  • sp_albumtype +: api.h +
  • +
  • sp_artistbrowse_type +: api.h +
  • +
  • sp_availability +: api.h +
  • +
  • sp_bitrate +: api.h +
  • +
  • sp_connection_rules +: api.h +
  • +
  • sp_connection_type +: api.h +
  • +
  • sp_connectionstate +: api.h +
  • +
  • sp_error +: api.h +
  • +
  • sp_image_size +: api.h +
  • +
  • sp_imageformat +: api.h +
  • +
  • sp_linktype +: api.h +
  • +
  • sp_playlist_offline_status +: api.h +
  • +
  • sp_playlist_type +: api.h +
  • +
  • sp_relation_type +: api.h +
  • +
  • sp_sampletype +: api.h +
  • +
  • sp_search_type +: api.h +
  • +
  • sp_toplistregion +: api.h +
  • +
  • sp_toplisttype +: api.h +
  • +
  • sp_track_offline_status +: api.h +
  • +
+
+
+Generated on Wed Jun 13 2012 14:22:40.
+Copyright © 2006–2012 Spotify Ltd
+ + diff --git a/libspotify/docs/html/globals_eval.html b/libspotify/docs/html/globals_eval.html new file mode 100644 index 0000000..5e2e3d6 --- /dev/null +++ b/libspotify/docs/html/globals_eval.html @@ -0,0 +1,371 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/globals_func.html b/libspotify/docs/html/globals_func.html new file mode 100644 index 0000000..1e73c62 --- /dev/null +++ b/libspotify/docs/html/globals_func.html @@ -0,0 +1,758 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/globals_type.html b/libspotify/docs/html/globals_type.html new file mode 100644 index 0000000..ea002e4 --- /dev/null +++ b/libspotify/docs/html/globals_type.html @@ -0,0 +1,194 @@ + + + + +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 -

    +
  • search_complete_cb +: api.h +
  • +
  • sp_album +: api.h +
  • +
  • sp_albumbrowse +: api.h +
  • +
  • sp_artist +: api.h +
  • +
  • sp_artistbrowse +: api.h +
  • +
  • sp_artistbrowse_type +: api.h +
  • +
  • sp_audio_buffer_stats +: api.h +
  • +
  • sp_audioformat +: api.h +
  • +
  • sp_bitrate +: api.h +
  • +
  • sp_connection_rules +: api.h +
  • +
  • sp_connection_type +: api.h +
  • +
  • sp_connectionstate +: api.h +
  • +
  • sp_error +: api.h +
  • +
  • sp_image +: api.h +
  • +
  • sp_image_size +: api.h +
  • +
  • sp_inbox +: api.h +
  • +
  • sp_link +: api.h +
  • +
  • sp_offline_sync_status +: api.h +
  • +
  • sp_playlist +: api.h +
  • +
  • sp_playlist_callbacks +: api.h +
  • +
  • sp_playlist_offline_status +: api.h +
  • +
  • sp_playlist_type +: api.h +
  • +
  • sp_playlistcontainer +: api.h +
  • +
  • sp_playlistcontainer_callbacks +: api.h +
  • +
  • sp_relation_type +: api.h +
  • +
  • sp_sampletype +: api.h +
  • +
  • sp_search +: api.h +
  • +
  • sp_search_type +: api.h +
  • +
  • sp_session +: api.h +
  • +
  • sp_session_callbacks +: api.h +
  • +
  • sp_session_config +: api.h +
  • +
  • sp_subscribers +: api.h +
  • +
  • sp_toplistbrowse +: api.h +
  • +
  • sp_track +: api.h +
  • +
  • sp_track_availability +: api.h +
  • +
  • sp_track_offline_status +: api.h +
  • +
  • sp_user +: api.h +
  • +
+ + +

- t -

    +
  • toplistbrowse_complete_cb +: api.h +
  • +
+
+
+Generated on Wed Jun 13 2012 14:22:40.
+Copyright © 2006–2012 Spotify Ltd
+ + diff --git a/libspotify/docs/html/group__album.html b/libspotify/docs/html/group__album.html new file mode 100644 index 0000000..a5f431c --- /dev/null +++ b/libspotify/docs/html/group__album.html @@ -0,0 +1,342 @@ + + + + +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

+ +
+
+ + + + +
enum sp_albumtype
+
+
+

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
+ + diff --git a/libspotify/docs/html/group__albumbrowse.html b/libspotify/docs/html/group__albumbrowse.html new file mode 100644 index 0000000..6f9a056 --- /dev/null +++ b/libspotify/docs/html/group__albumbrowse.html @@ -0,0 +1,475 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/group__artist.html b/libspotify/docs/html/group__artist.html new file mode 100644 index 0000000..bd9ae9b --- /dev/null +++ b/libspotify/docs/html/group__artist.html @@ -0,0 +1,185 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/group__artistbrowse.html b/libspotify/docs/html/group__artistbrowse.html new file mode 100644 index 0000000..55241d3 --- /dev/null +++ b/libspotify/docs/html/group__artistbrowse.html @@ -0,0 +1,654 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/group__error.html b/libspotify/docs/html/group__error.html new file mode 100644 index 0000000..1b264f8 --- /dev/null +++ b/libspotify/docs/html/group__error.html @@ -0,0 +1,291 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/group__image.html b/libspotify/docs/html/group__image.html new file mode 100644 index 0000000..cd445c8 --- /dev/null +++ b/libspotify/docs/html/group__image.html @@ -0,0 +1,457 @@ + + + + +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

+ +
+
+ + + + +
enum sp_imageformat
+
+
+

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
+ + diff --git a/libspotify/docs/html/group__inbox.html b/libspotify/docs/html/group__inbox.html new file mode 100644 index 0000000..1f6b088 --- /dev/null +++ b/libspotify/docs/html/group__inbox.html @@ -0,0 +1,219 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/group__link.html b/libspotify/docs/html/group__link.html new file mode 100644 index 0000000..f6c66ee --- /dev/null +++ b/libspotify/docs/html/group__link.html @@ -0,0 +1,761 @@ + + + + +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

+ +
+
+ + + + +
enum sp_linktype
+
+
+

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
+ + diff --git a/libspotify/docs/html/group__playlist.html b/libspotify/docs/html/group__playlist.html new file mode 100644 index 0000000..0476e43 --- /dev/null +++ b/libspotify/docs/html/group__playlist.html @@ -0,0 +1,2027 @@ + + + + +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

+ +
+
+ + + + +
typedef struct sp_playlist_callbacks sp_playlist_callbacks
+
+
+

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
+ + diff --git a/libspotify/docs/html/group__search.html b/libspotify/docs/html/group__search.html new file mode 100644 index 0000000..588937b --- /dev/null +++ b/libspotify/docs/html/group__search.html @@ -0,0 +1,831 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/group__session.html b/libspotify/docs/html/group__session.html new file mode 100644 index 0000000..182074f --- /dev/null +++ b/libspotify/docs/html/group__session.html @@ -0,0 +1,2301 @@ + + + + +libspotify: Session handling + + + + + + +
+ +
+

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

+ +
+
+ + + + +
typedef enum sp_artistbrowse_type sp_artistbrowse_type
+
+
+

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

+ +
+
+ +
+
+ + + + +
typedef struct sp_audio_buffer_stats sp_audio_buffer_stats
+
+
+

Buffer stats used by get_audio_buffer_stats callback

+ +
+
+ +
+
+ + + + +
typedef struct sp_audioformat sp_audioformat
+
+
+

Audio format descriptor

+ +
+
+ +
+
+ + + + +
typedef enum sp_bitrate sp_bitrate
+
+
+

Bitrate definitions for music streaming

+ +
+
+ +
+
+ + + + +
typedef enum sp_connection_rules sp_connection_rules
+
+
+

Connection rules, bitwise OR of flags

+

The default is SP_CONNECTION_RULE_NETWORK | SP_CONNECTION_RULE_ALLOW_SYNC

+ +
+
+ +
+
+ + + + +
typedef enum sp_connection_type sp_connection_type
+
+
+

Current connection type set using sp_session_set_connection_type()

+ +
+
+ +
+
+ + + + +
typedef enum sp_connectionstate sp_connectionstate
+
+
+

Describes the current state of the connection

+ +
+
+ +
+
+ + + + +
typedef enum sp_image_size sp_image_size
+
+
+

Image size

+ +
+
+ +
+ +
+

Offline sync status

+ +
+
+ +
+ +
+

Playlist offline status

+ +
+
+ +
+
+ + + + +
typedef enum sp_playlist_type sp_playlist_type
+
+
+

Playlist types

+ +
+
+ +
+
+ + + + +
typedef enum sp_sampletype sp_sampletype
+
+
+

Sample type descriptor

+ +
+
+ +
+
+ + + + +
typedef enum sp_search_type sp_search_type
+
+
+

Search types

+ +
+
+ +
+
+ + + + +
typedef struct sp_session_callbacks sp_session_callbacks
+
+
+

Session callbacks

+

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

+ +
+
+ +
+
+ + + + +
typedef struct sp_session_config sp_session_config
+
+
+

Session config

+ +
+
+ +
+
+ + + + +
typedef struct sp_subscribers sp_subscribers
+
+
+

List of subscribers returned by sp_playlist_subscribers()

+ +
+
+ +
+
+ + + + +
typedef enum sp_availability sp_track_availability
+
+
+

Track availability

+ +
+
+ +
+ +
+

Track offline status

+ +
+
+

Enumeration Type Documentation

+ +
+
+ + + + +
enum sp_artistbrowse_type
+
+
+

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

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_availability
+
+
+

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.

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_connection_rules
+
+
+

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.

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_connection_type
+
+
+

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.

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_connectionstate
+
+
+

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.

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_image_size
+
+
+

Image size

+
Enumerator:
+ + + +
SP_IMAGE_SIZE_NORMAL  +

Normal image size.

+
SP_IMAGE_SIZE_SMALL  +

Small image size.

+
SP_IMAGE_SIZE_LARGE  +

Large image size.

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_playlist_offline_status
+
+
+

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.

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_playlist_type
+
+
+

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.

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_sampletype
+
+
+

Sample type descriptor

+
Enumerator:
+ +
SP_SAMPLETYPE_INT16_NATIVE_ENDIAN  +

16-bit signed integer samples

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_search_type
+
+
+

Search types

+ +
+
+ +
+
+ + + + +
enum sp_track_offline_status
+
+
+

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
+ + diff --git a/libspotify/docs/html/group__toplist.html b/libspotify/docs/html/group__toplist.html new file mode 100644 index 0000000..0ca58b4 --- /dev/null +++ b/libspotify/docs/html/group__toplist.html @@ -0,0 +1,572 @@ + + + + +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

+ +
+
+ + + + +
enum sp_toplistregion
+
+
+

Special toplist regions

+
Enumerator:
+ + +
SP_TOPLIST_REGION_EVERYWHERE  +

Global toplist.

+
SP_TOPLIST_REGION_USER  +

Toplist for a given user.

+
+
+
+ +
+
+ +
+
+ + + + +
enum sp_toplisttype
+
+
+

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
+ + diff --git a/libspotify/docs/html/group__track.html b/libspotify/docs/html/group__track.html new file mode 100644 index 0000000..a09131f --- /dev/null +++ b/libspotify/docs/html/group__track.html @@ -0,0 +1,738 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/group__types.html b/libspotify/docs/html/group__types.html new file mode 100644 index 0000000..81dddc0 --- /dev/null +++ b/libspotify/docs/html/group__types.html @@ -0,0 +1,80 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/group__user.html b/libspotify/docs/html/group__user.html new file mode 100644 index 0000000..4c4b486 --- /dev/null +++ b/libspotify/docs/html/group__user.html @@ -0,0 +1,237 @@ + + + + +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

+ +
+
+ + + + +
typedef enum sp_relation_type sp_relation_type
+
+
+

User relation type

+ +
+
+

Enumeration Type Documentation

+ +
+
+ + + + +
enum sp_relation_type
+
+
+

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
+ + diff --git a/libspotify/docs/html/index.html b/libspotify/docs/html/index.html new file mode 100644 index 0000000..1348d19 --- /dev/null +++ b/libspotify/docs/html/index.html @@ -0,0 +1,88 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/jukebox_8c-example.html b/libspotify/docs/html/jukebox_8c-example.html new file mode 100644 index 0000000..1964dcf --- /dev/null +++ b/libspotify/docs/html/jukebox_8c-example.html @@ -0,0 +1,458 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/modules.html b/libspotify/docs/html/modules.html new file mode 100644 index 0000000..3480f06 --- /dev/null +++ b/libspotify/docs/html/modules.html @@ -0,0 +1,49 @@ + + + + +libspotify: Module Index + + + + + + +
+
+

Modules

+
+ +
+Generated on Wed Jun 13 2012 14:22:40.
+Copyright © 2006–2012 Spotify Ltd
+ + diff --git a/libspotify/docs/html/nav_f.png b/libspotify/docs/html/nav_f.png new file mode 100644 index 0000000..1b07a16 Binary files /dev/null and b/libspotify/docs/html/nav_f.png differ diff --git a/libspotify/docs/html/nav_h.png b/libspotify/docs/html/nav_h.png new file mode 100644 index 0000000..01f5fa6 Binary files /dev/null and b/libspotify/docs/html/nav_h.png differ diff --git a/libspotify/docs/html/open.png b/libspotify/docs/html/open.png new file mode 100644 index 0000000..7b35d2c Binary files /dev/null and b/libspotify/docs/html/open.png differ diff --git a/libspotify/docs/html/search_8c-example.html b/libspotify/docs/html/search_8c-example.html new file mode 100644 index 0000000..8f5ac19 --- /dev/null +++ b/libspotify/docs/html/search_8c-example.html @@ -0,0 +1,130 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/structsp__audio__buffer__stats.html b/libspotify/docs/html/structsp__audio__buffer__stats.html new file mode 100644 index 0000000..c02b748 --- /dev/null +++ b/libspotify/docs/html/structsp__audio__buffer__stats.html @@ -0,0 +1,60 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/structsp__audioformat.html b/libspotify/docs/html/structsp__audioformat.html new file mode 100644 index 0000000..8bf5286 --- /dev/null +++ b/libspotify/docs/html/structsp__audioformat.html @@ -0,0 +1,66 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/structsp__offline__sync__status.html b/libspotify/docs/html/structsp__offline__sync__status.html new file mode 100644 index 0000000..31e8582 --- /dev/null +++ b/libspotify/docs/html/structsp__offline__sync__status.html @@ -0,0 +1,145 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/structsp__playlist__callbacks.html b/libspotify/docs/html/structsp__playlist__callbacks.html new file mode 100644 index 0000000..851e257 --- /dev/null +++ b/libspotify/docs/html/structsp__playlist__callbacks.html @@ -0,0 +1,375 @@ + + + + +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()
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_playlist_callbacks::playlist_metadata_updated)(sp_playlist *pl, void *userdata)
+
+
+

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()
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_playlist_callbacks::playlist_renamed)(sp_playlist *pl, void *userdata)
+
+
+

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()
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_playlist_callbacks::playlist_state_changed)(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
  • +
+
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()
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_playlist_callbacks::subscribers_changed)(sp_playlist *pl, void *userdata)
+
+
+

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
+ + diff --git a/libspotify/docs/html/structsp__playlistcontainer__callbacks.html b/libspotify/docs/html/structsp__playlistcontainer__callbacks.html new file mode 100644 index 0000000..06fb995 --- /dev/null +++ b/libspotify/docs/html/structsp__playlistcontainer__callbacks.html @@ -0,0 +1,156 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/structsp__session__callbacks.html b/libspotify/docs/html/structsp__session__callbacks.html new file mode 100644 index 0000000..6aa70d4 --- /dev/null +++ b/libspotify/docs/html/structsp__session__callbacks.html @@ -0,0 +1,539 @@ + + + + +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

+ +
+
+ + + + +
void( * sp_session_callbacks::connection_error)(sp_session *session, sp_error error)
+
+
+

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.
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::end_of_track)(sp_session *session)
+
+
+

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
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::logged_in)(sp_session *session, sp_error error)
+
+
+

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
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::logged_out)(sp_session *session)
+
+
+

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.
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::metadata_updated)(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.

+
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!
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::offline_error)(sp_session *session, sp_error error)
+
+
+

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
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::play_token_lost)(sp_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
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::private_session_mode_changed)(sp_session *session, bool is_private)
+
+
+

Called when there is a change in the private session mode

+
Parameters:
+ + + +
[in] session Session
[in] isPrivate True if in private session, false otherwhise
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::scrobble_error)(sp_session *session, sp_error error)
+
+
+

Called when there is a scrobble error event

+
Parameters:
+ + + +
[in] session Session
[in] error Scrobble error. Currently SP_ERROR_LASTFM_AUTH_ERROR.
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::start_playback)(sp_session *session)
+
+
+

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
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::stop_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()
+
+This function is called from an internal session thread - you need to have proper synchronization!
+
+This function must never block.
+
Parameters:
+ + +
[in] session Session
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::streaming_error)(sp_session *session, sp_error error)
+
+
+

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
+
+
+ +
+
+ +
+
+ + + + +
void( * sp_session_callbacks::userinfo_updated)(sp_session *session)
+
+
+

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
+ + diff --git a/libspotify/docs/html/structsp__session__config.html b/libspotify/docs/html/structsp__session__config.html new file mode 100644 index 0000000..1c639e7 --- /dev/null +++ b/libspotify/docs/html/structsp__session__config.html @@ -0,0 +1,253 @@ + + + + +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

+ +
+
+ + + + +
const char* sp_session_config::ca_certs_filename
+
+
+

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.

+ +
+
+ +
+
+ + + + +
const char* sp_session_config::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

+ +
+
+ +
+ +
+

Compress local copy of playlists, reduces disk space usage

+ +
+
+ +
+
+ + + + +
const char* sp_session_config::device_id
+
+
+

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.

+ +
+
+ +
+
+ + + + +
const char* sp_session_config::proxy
+
+
+

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

+ +
+
+ +
+
+ + + + +
const char* sp_session_config::proxy_password
+
+
+

Password to authenticate with proxy server

+ +
+
+ +
+
+ + + + +
const char* sp_session_config::proxy_username
+
+
+

Username to authenticate with proxy server

+ +
+
+ +
+
+ + + + +
const char* sp_session_config::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 char* sp_session_config::tracefile
+
+
+

Path to API trace file

+ +
+
+ +
+
+ + + + +
const char* sp_session_config::user_agent
+
+
+

"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
+ + diff --git a/libspotify/docs/html/structsp__subscribers.html b/libspotify/docs/html/structsp__subscribers.html new file mode 100644 index 0000000..81a7ad1 --- /dev/null +++ b/libspotify/docs/html/structsp__subscribers.html @@ -0,0 +1,60 @@ + + + + +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
+ + diff --git a/libspotify/docs/html/tab_a.png b/libspotify/docs/html/tab_a.png new file mode 100644 index 0000000..2d99ef2 Binary files /dev/null and b/libspotify/docs/html/tab_a.png differ diff --git a/libspotify/docs/html/tab_b.png b/libspotify/docs/html/tab_b.png new file mode 100644 index 0000000..b2c3d2b Binary files /dev/null and b/libspotify/docs/html/tab_b.png differ diff --git a/libspotify/docs/html/tab_h.png b/libspotify/docs/html/tab_h.png new file mode 100644 index 0000000..c11f48f Binary files /dev/null and b/libspotify/docs/html/tab_h.png differ diff --git a/libspotify/docs/html/tab_s.png b/libspotify/docs/html/tab_s.png new file mode 100644 index 0000000..978943a Binary files /dev/null and b/libspotify/docs/html/tab_s.png differ diff --git a/libspotify/docs/html/tabs.css b/libspotify/docs/html/tabs.css new file mode 100644 index 0000000..2192056 --- /dev/null +++ b/libspotify/docs/html/tabs.css @@ -0,0 +1,59 @@ +.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); +} diff --git a/libspotify/docs/html/toplist_8c-example.html b/libspotify/docs/html/toplist_8c-example.html new file mode 100644 index 0000000..bf18b2f --- /dev/null +++ b/libspotify/docs/html/toplist_8c-example.html @@ -0,0 +1,130 @@ + + + + +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
+ + diff --git a/libspotify/docs/images/spotify-core-logo-128x128.png b/libspotify/docs/images/spotify-core-logo-128x128.png new file mode 100644 index 0000000..e52ecc4 Binary files /dev/null and b/libspotify/docs/images/spotify-core-logo-128x128.png differ diff --git a/libspotify/docs/images/spotify-core.txt b/libspotify/docs/images/spotify-core.txt new file mode 100644 index 0000000..a264b1f --- /dev/null +++ b/libspotify/docs/images/spotify-core.txt @@ -0,0 +1 @@ +Uses SPOTIFY(R) CORE diff --git a/libspotify/examples/Makefile b/libspotify/examples/Makefile new file mode 100644 index 0000000..06a7ce0 --- /dev/null +++ b/libspotify/examples/Makefile @@ -0,0 +1,10 @@ +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 diff --git a/libspotify/examples/Randomify/English.lproj/InfoPlist.strings b/libspotify/examples/Randomify/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/libspotify/examples/Randomify/English.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/libspotify/examples/Randomify/English.lproj/MainMenu.xib b/libspotify/examples/Randomify/English.lproj/MainMenu.xib new file mode 100644 index 0000000..3b1a537 --- /dev/null +++ b/libspotify/examples/Randomify/English.lproj/MainMenu.xib @@ -0,0 +1,4164 @@ + + + + 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 + + diff --git a/libspotify/examples/Randomify/Randomify-Info.plist b/libspotify/examples/Randomify/Randomify-Info.plist new file mode 100644 index 0000000..7c0ec30 --- /dev/null +++ b/libspotify/examples/Randomify/Randomify-Info.plist @@ -0,0 +1,32 @@ + + + + + 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 + + diff --git a/libspotify/examples/Randomify/Randomify.xcodeproj/project.pbxproj b/libspotify/examples/Randomify/Randomify.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b0bf654 --- /dev/null +++ b/libspotify/examples/Randomify/Randomify.xcodeproj/project.pbxproj @@ -0,0 +1,326 @@ +// !$*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 */; +} diff --git a/libspotify/examples/Randomify/RandomifyAppDelegate.h b/libspotify/examples/Randomify/RandomifyAppDelegate.h new file mode 100644 index 0000000..543b82c --- /dev/null +++ b/libspotify/examples/Randomify/RandomifyAppDelegate.h @@ -0,0 +1,52 @@ +/** + * 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 diff --git a/libspotify/examples/Randomify/RandomifyAppDelegate.m b/libspotify/examples/Randomify/RandomifyAppDelegate.m new file mode 100644 index 0000000..8dabb63 --- /dev/null +++ b/libspotify/examples/Randomify/RandomifyAppDelegate.m @@ -0,0 +1,155 @@ +/** + * 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 diff --git a/libspotify/examples/Randomify/Randomify_Prefix.pch b/libspotify/examples/Randomify/Randomify_Prefix.pch new file mode 100644 index 0000000..0aa4e38 --- /dev/null +++ b/libspotify/examples/Randomify/Randomify_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'Randomify' target in the 'Randomify' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/libspotify/examples/Randomify/SpotifyPlaylist.h b/libspotify/examples/Randomify/SpotifyPlaylist.h new file mode 100644 index 0000000..8c00ac5 --- /dev/null +++ b/libspotify/examples/Randomify/SpotifyPlaylist.h @@ -0,0 +1,32 @@ +/** + * 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 diff --git a/libspotify/examples/Randomify/SpotifyPlaylist.m b/libspotify/examples/Randomify/SpotifyPlaylist.m new file mode 100644 index 0000000..df6adbe --- /dev/null +++ b/libspotify/examples/Randomify/SpotifyPlaylist.m @@ -0,0 +1,68 @@ +/** + * 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 diff --git a/libspotify/examples/Randomify/SpotifySession.h b/libspotify/examples/Randomify/SpotifySession.h new file mode 100644 index 0000000..d8f5569 --- /dev/null +++ b/libspotify/examples/Randomify/SpotifySession.h @@ -0,0 +1,52 @@ +/** + * 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 diff --git a/libspotify/examples/Randomify/SpotifySession.m b/libspotify/examples/Randomify/SpotifySession.m new file mode 100644 index 0000000..a6da422 --- /dev/null +++ b/libspotify/examples/Randomify/SpotifySession.m @@ -0,0 +1,288 @@ +/** + * 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 diff --git a/libspotify/examples/Randomify/SpotifyTrack.h b/libspotify/examples/Randomify/SpotifyTrack.h new file mode 100644 index 0000000..bf36e35 --- /dev/null +++ b/libspotify/examples/Randomify/SpotifyTrack.h @@ -0,0 +1,23 @@ +/** + * 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 diff --git a/libspotify/examples/Randomify/SpotifyTrack.m b/libspotify/examples/Randomify/SpotifyTrack.m new file mode 100644 index 0000000..4ca9c82 --- /dev/null +++ b/libspotify/examples/Randomify/SpotifyTrack.m @@ -0,0 +1,54 @@ +/** + * 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 diff --git a/libspotify/examples/Randomify/main.m b/libspotify/examples/Randomify/main.m new file mode 100644 index 0000000..6319250 --- /dev/null +++ b/libspotify/examples/Randomify/main.m @@ -0,0 +1,14 @@ +// +// 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); +} diff --git a/libspotify/examples/appkey.c b/libspotify/examples/appkey.c new file mode 100644 index 0000000..a91aad6 --- /dev/null +++ b/libspotify/examples/appkey.c @@ -0,0 +1,3 @@ +#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 diff --git a/libspotify/examples/common.mk b/libspotify/examples/common.mk new file mode 100644 index 0000000..e41311c --- /dev/null +++ b/libspotify/examples/common.mk @@ -0,0 +1,51 @@ +# 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) diff --git a/libspotify/examples/jukebox/Makefile b/libspotify/examples/jukebox/Makefile new file mode 100644 index 0000000..2e1e7e5 --- /dev/null +++ b/libspotify/examples/jukebox/Makefile @@ -0,0 +1,38 @@ +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 diff --git a/libspotify/examples/jukebox/alsa-audio.c b/libspotify/examples/jukebox/alsa-audio.c new file mode 100644 index 0000000..e31b482 --- /dev/null +++ b/libspotify/examples/jukebox/alsa-audio.c @@ -0,0 +1,228 @@ +/* + * 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); +} + diff --git a/libspotify/examples/jukebox/audio.c b/libspotify/examples/jukebox/audio.c new file mode 100644 index 0000000..fa437a5 --- /dev/null +++ b/libspotify/examples/jukebox/audio.c @@ -0,0 +1,60 @@ +/* + * 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); +} diff --git a/libspotify/examples/jukebox/audio.h b/libspotify/examples/jukebox/audio.h new file mode 100644 index 0000000..1705c2c --- /dev/null +++ b/libspotify/examples/jukebox/audio.h @@ -0,0 +1,57 @@ +/* + * 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_ */ diff --git a/libspotify/examples/jukebox/dummy-audio.c b/libspotify/examples/jukebox/dummy-audio.c new file mode 100644 index 0000000..65cdf61 --- /dev/null +++ b/libspotify/examples/jukebox/dummy-audio.c @@ -0,0 +1,60 @@ +/* + * 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); +} + + diff --git a/libspotify/examples/jukebox/jukebox.c b/libspotify/examples/jukebox/jukebox.c new file mode 100644 index 0000000..2c44d8f --- /dev/null +++ b/libspotify/examples/jukebox/jukebox.c @@ -0,0 +1,590 @@ +/** + * 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; +} diff --git a/libspotify/examples/jukebox/openal-audio.c b/libspotify/examples/jukebox/openal-audio.c new file mode 100644 index 0000000..49ea8d5 --- /dev/null +++ b/libspotify/examples/jukebox/openal-audio.c @@ -0,0 +1,153 @@ +/* + * 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); +} + + diff --git a/libspotify/examples/jukebox/osx-audio.c b/libspotify/examples/jukebox/osx-audio.c new file mode 100644 index 0000000..0825a0d --- /dev/null +++ b/libspotify/examples/jukebox/osx-audio.c @@ -0,0 +1,91 @@ +/* + * 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"); +} + diff --git a/libspotify/examples/jukebox/osx/jukebox.xcodeproj/project.pbxproj b/libspotify/examples/jukebox/osx/jukebox.xcodeproj/project.pbxproj new file mode 100644 index 0000000..99a3fd7 --- /dev/null +++ b/libspotify/examples/jukebox/osx/jukebox.xcodeproj/project.pbxproj @@ -0,0 +1,242 @@ +// !$*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 */; +} diff --git a/libspotify/examples/jukebox/playtrack.c b/libspotify/examples/jukebox/playtrack.c new file mode 100644 index 0000000..51d2910 --- /dev/null +++ b/libspotify/examples/jukebox/playtrack.c @@ -0,0 +1,350 @@ +/** + * 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; +} diff --git a/libspotify/examples/jukebox/queue.h b/libspotify/examples/jukebox/queue.h new file mode 100644 index 0000000..b0e6b38 --- /dev/null +++ b/libspotify/examples/jukebox/queue.h @@ -0,0 +1,557 @@ +/* + * 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 */ diff --git a/libspotify/examples/localfiles/Makefile b/libspotify/examples/localfiles/Makefile new file mode 100644 index 0000000..80e1adb --- /dev/null +++ b/libspotify/examples/localfiles/Makefile @@ -0,0 +1,13 @@ +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 diff --git a/libspotify/examples/localfiles/main.c b/libspotify/examples/localfiles/main.c new file mode 100644 index 0000000..c91dfe7 --- /dev/null +++ b/libspotify/examples/localfiles/main.c @@ -0,0 +1,204 @@ +/** + * 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; +} diff --git a/libspotify/examples/localfiles/main.h b/libspotify/examples/localfiles/main.h new file mode 100644 index 0000000..133dc70 --- /dev/null +++ b/libspotify/examples/localfiles/main.h @@ -0,0 +1,4 @@ +#ifndef __MAIN_H__ +#define __MAIN_H__ +#include +#endif diff --git a/libspotify/examples/spshell/Makefile b/libspotify/examples/spshell/Makefile new file mode 100644 index 0000000..b05e795 --- /dev/null +++ b/libspotify/examples/spshell/Makefile @@ -0,0 +1,22 @@ +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 diff --git a/libspotify/examples/spshell/browse.c b/libspotify/examples/spshell/browse.c new file mode 100644 index 0000000..b59546f --- /dev/null +++ b/libspotify/examples/spshell/browse.c @@ -0,0 +1,341 @@ +/** + * 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; +} diff --git a/libspotify/examples/spshell/cmd.c b/libspotify/examples/spshell/cmd.c new file mode 100644 index 0000000..ea52fc3 --- /dev/null +++ b/libspotify/examples/spshell/cmd.c @@ -0,0 +1,145 @@ +/** + * 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; +} diff --git a/libspotify/examples/spshell/cmd.h b/libspotify/examples/spshell/cmd.h new file mode 100644 index 0000000..35f2afe --- /dev/null +++ b/libspotify/examples/spshell/cmd.h @@ -0,0 +1,78 @@ +/** + * 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__ diff --git a/libspotify/examples/spshell/inbox.c b/libspotify/examples/spshell/inbox.c new file mode 100644 index 0000000..4ecbf3e --- /dev/null +++ b/libspotify/examples/spshell/inbox.c @@ -0,0 +1,119 @@ +/** + * 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; +} diff --git a/libspotify/examples/spshell/osx/spshell.xcodeproj/project.pbxproj b/libspotify/examples/spshell/osx/spshell.xcodeproj/project.pbxproj new file mode 100644 index 0000000..91ae552 --- /dev/null +++ b/libspotify/examples/spshell/osx/spshell.xcodeproj/project.pbxproj @@ -0,0 +1,276 @@ +// !$*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 */; +} diff --git a/libspotify/examples/spshell/playlist.c b/libspotify/examples/spshell/playlist.c new file mode 100644 index 0000000..1a739c2 --- /dev/null +++ b/libspotify/examples/spshell/playlist.c @@ -0,0 +1,445 @@ +/** + * 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; +} diff --git a/libspotify/examples/spshell/scrobbling.c b/libspotify/examples/spshell/scrobbling.c new file mode 100644 index 0000000..bf73203 --- /dev/null +++ b/libspotify/examples/spshell/scrobbling.c @@ -0,0 +1,157 @@ +/** + * 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; +} + diff --git a/libspotify/examples/spshell/search.c b/libspotify/examples/spshell/search.c new file mode 100644 index 0000000..606696b --- /dev/null +++ b/libspotify/examples/spshell/search.c @@ -0,0 +1,148 @@ +/** + * 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; +} diff --git a/libspotify/examples/spshell/spshell.c b/libspotify/examples/spshell/spshell.c new file mode 100644 index 0000000..d03fe43 --- /dev/null +++ b/libspotify/examples/spshell/spshell.c @@ -0,0 +1,306 @@ +/** + * 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; +} diff --git a/libspotify/examples/spshell/spshell.h b/libspotify/examples/spshell/spshell.h new file mode 100644 index 0000000..76f9d9e --- /dev/null +++ b/libspotify/examples/spshell/spshell.h @@ -0,0 +1,76 @@ +/** + * 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__ diff --git a/libspotify/examples/spshell/spshell_posix.c b/libspotify/examples/spshell/spshell_posix.c new file mode 100644 index 0000000..5983a7e --- /dev/null +++ b/libspotify/examples/spshell/spshell_posix.c @@ -0,0 +1,263 @@ +/** + * 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); +} diff --git a/libspotify/examples/spshell/spshell_win32.c b/libspotify/examples/spshell/spshell_win32.c new file mode 100644 index 0000000..f94ac42 --- /dev/null +++ b/libspotify/examples/spshell/spshell_win32.c @@ -0,0 +1,206 @@ +/** + * 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(); +} diff --git a/libspotify/examples/spshell/star.c b/libspotify/examples/spshell/star.c new file mode 100644 index 0000000..7eb647c --- /dev/null +++ b/libspotify/examples/spshell/star.c @@ -0,0 +1,104 @@ +/** + * 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; +} diff --git a/libspotify/examples/spshell/test.c b/libspotify/examples/spshell/test.c new file mode 100644 index 0000000..c671e79 --- /dev/null +++ b/libspotify/examples/spshell/test.c @@ -0,0 +1,855 @@ +/** + * 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; +} diff --git a/libspotify/examples/spshell/toplist.c b/libspotify/examples/spshell/toplist.c new file mode 100644 index 0000000..0314fea --- /dev/null +++ b/libspotify/examples/spshell/toplist.c @@ -0,0 +1,136 @@ +/** + * 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; +} diff --git a/libspotify/examples/spshell/win32/spshell.vcproj b/libspotify/examples/spshell/win32/spshell.vcproj new file mode 100644 index 0000000..c5072d9 --- /dev/null +++ b/libspotify/examples/spshell/win32/spshell.vcproj @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libspotify/examples/spshell/win32/spshell.vcxproj b/libspotify/examples/spshell/win32/spshell.vcxproj new file mode 100644 index 0000000..2fb741c --- /dev/null +++ b/libspotify/examples/spshell/win32/spshell.vcxproj @@ -0,0 +1,117 @@ + + + + + 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 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libspotify/examples/spshell/win32/spshell.vcxproj.filters b/libspotify/examples/spshell/win32/spshell.vcxproj.filters new file mode 100644 index 0000000..bbf7af3 --- /dev/null +++ b/libspotify/examples/spshell/win32/spshell.vcxproj.filters @@ -0,0 +1,60 @@ + + + + + {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 + + + \ No newline at end of file diff --git a/libspotify/examples/stub/Makefile b/libspotify/examples/stub/Makefile new file mode 100644 index 0000000..7f3e55e --- /dev/null +++ b/libspotify/examples/stub/Makefile @@ -0,0 +1,13 @@ +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 diff --git a/libspotify/examples/stub/main.c b/libspotify/examples/stub/main.c new file mode 100644 index 0000000..c649ccc --- /dev/null +++ b/libspotify/examples/stub/main.c @@ -0,0 +1,193 @@ +/** + * 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; +} diff --git a/libspotify/examples/stub/main.h b/libspotify/examples/stub/main.h new file mode 100644 index 0000000..133dc70 --- /dev/null +++ b/libspotify/examples/stub/main.h @@ -0,0 +1,4 @@ +#ifndef __MAIN_H__ +#define __MAIN_H__ +#include +#endif diff --git a/libspotify/include/libspotify/api.h b/libspotify/include/libspotify/api.h new file mode 100644 index 0000000..7ed7a16 --- /dev/null +++ b/libspotify/include/libspotify/api.h @@ -0,0 +1,3959 @@ +/* + * 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. + */ diff --git a/libspotify/lib/libspotify.dll b/libspotify/lib/libspotify.dll new file mode 100644 index 0000000..97071d7 Binary files /dev/null and b/libspotify/lib/libspotify.dll differ diff --git a/libspotify/lib/libspotify.lib b/libspotify/lib/libspotify.lib new file mode 100644 index 0000000..eb70784 Binary files /dev/null and b/libspotify/lib/libspotify.lib differ diff --git a/libspotify/licenses.xhtml b/libspotify/licenses.xhtml new file mode 100644 index 0000000..a9a0c4e --- /dev/null +++ b/libspotify/licenses.xhtml @@ -0,0 +1,564 @@ + + + + + + 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. +
    3. + "Author" means the individual or entity that offers the Work under the terms of this License. +
    4. +
    5. + "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works. +
    6. +
    7. + "Executable Files" refer to the executables, binary files, configuration and any required data files included in the Work. +
    8. +
    9. + "Publisher" means the provider of the website, magazine, CD-ROM, DVD or other medium from or by which the Work is obtained by You. +
    10. +
    11. + "Source Code" refers to the collection of source code and configuration files used to create the Executable Files. +
    12. +
    13. + "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. +
    14. +
    15. + "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. +
    16. +
    17. + "You" is you, an individual or entity wishing to use the Work and exercise your rights under this License. +
    18. +
    +
  2. +
  3. + 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. +
  4. +
  5. + 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. +
    3. + 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. +
    4. +
    5. + 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. +
    6. +
    7. + 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. +
    8. +
    9. + 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. +
    10. +
    + 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. +
  6. +
  7. + 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. +
  8. +
  9. + 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. +
    3. + You agree not to advertise or in any way imply that this Work is a product of Your own. +
    4. +
    5. + 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. +
    6. +
    7. + 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. +
    8. +
    9. + 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. +
    10. +
    11. + 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. +
    12. +
    +
  10. +
  11. + 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. +
  12. +
  13. + 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. +
  14. +
  15. + 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. +
  16. +
  17. + 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. +
    3. + 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. +
    4. +
    5. + 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. +
    6. +
    +
  18. +
  19. + 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 +
  20. +
  21. + 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. +
    3. + 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. +
    4. +
    5. + 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. +
    6. +
    7. + 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 +
    8. +
    +
  22. +
+
+

+ 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. +
  3. + 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. +
  4. +
  5. + The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. +
  6. +
+

+ 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. +
  3. + Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +
  4. +
  5. + This notice may not be removed or altered from any source distribution. +
  6. +
+

+ 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. +

+
+ + diff --git a/licenses/JSON for Modern C++.txt b/licenses/JSON for Modern C++.txt new file mode 100644 index 0000000..8b0f700 --- /dev/null +++ b/licenses/JSON for Modern C++.txt @@ -0,0 +1,21 @@ +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. diff --git a/licenses/LibSpotify.txt b/licenses/LibSpotify.txt new file mode 100644 index 0000000..037cb0d --- /dev/null +++ b/licenses/LibSpotify.txt @@ -0,0 +1,3 @@ +For the current terms and conditions, please read: + +http://developer.spotify.com/en/libspotify/terms-of-use/ diff --git a/licenses/PFC.txt b/licenses/PFC.txt new file mode 100644 index 0000000..53076b8 --- /dev/null +++ b/licenses/PFC.txt @@ -0,0 +1,17 @@ +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. \ No newline at end of file diff --git a/licenses/WTL.txt b/licenses/WTL.txt new file mode 100644 index 0000000..6d2577a --- /dev/null +++ b/licenses/WTL.txt @@ -0,0 +1,22 @@ +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. diff --git a/licenses/fmt.txt b/licenses/fmt.txt new file mode 100644 index 0000000..f0ec3db --- /dev/null +++ b/licenses/fmt.txt @@ -0,0 +1,27 @@ +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. diff --git a/licenses/foobar2000 SDK.txt b/licenses/foobar2000 SDK.txt new file mode 100644 index 0000000..39cbcb4 --- /dev/null +++ b/licenses/foobar2000 SDK.txt @@ -0,0 +1,12 @@ +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. diff --git a/licenses/range-v3.txt b/licenses/range-v3.txt new file mode 100644 index 0000000..698193e --- /dev/null +++ b/licenses/range-v3.txt @@ -0,0 +1,151 @@ +======================================================== +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. +// diff --git a/licenses/span lite.txt b/licenses/span lite.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/licenses/span lite.txt @@ -0,0 +1,23 @@ +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. diff --git a/props/local_dependencies/libspotify.props b/props/local_dependencies/libspotify.props new file mode 100644 index 0000000..c83ad5f --- /dev/null +++ b/props/local_dependencies/libspotify.props @@ -0,0 +1,24 @@ + + + + + + <_PropertySheetDisplayName>libspotify + + + $(MainDir)libspotify\ + $(LibSpotifyDirectory)lib\ + $(LibSpotifyDirectory)include\ + libspotify.lib + + + $(LibSpotifyIncludeDirectory);$(IncludePath) + + + + $(LibSpotifyLibDirectory);%(AdditionalLibraryDirectories) + $(LibSpotifyDependencies);%(AdditionalDependencies) + + + + \ No newline at end of file diff --git a/props/submodules/fb2k_utils.props b/props/submodules/fb2k_utils.props new file mode 100644 index 0000000..e1fe0b2 --- /dev/null +++ b/props/submodules/fb2k_utils.props @@ -0,0 +1,15 @@ + + + + + + <_PropertySheetDisplayName>fb2k_utils + + + $(SolutionDir)..\submodules\fb2k_utils\ + $(QwrFb2kUtilsRootDir)props\ + + + $(QwrFb2kUtilsRootDir)src\;$(IncludePath) + + \ No newline at end of file diff --git a/props/submodules/submodules.props b/props/submodules/submodules.props new file mode 100644 index 0000000..e0e2046 --- /dev/null +++ b/props/submodules/submodules.props @@ -0,0 +1,14 @@ + + + + + + <_PropertySheetDisplayName>submodules + + + $(SolutionDir)..\submodules\ + + + $(SubmodulesDirectory);$(IncludePath) + + \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..0dfb756 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,8 @@ +### 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. diff --git a/scripts/call_wrapper.py b/scripts/call_wrapper.py new file mode 100644 index 0000000..39fdcd4 --- /dev/null +++ b/scripts/call_wrapper.py @@ -0,0 +1,30 @@ +#!/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 diff --git a/scripts/download_submodules.py b/scripts/download_submodules.py new file mode 100644 index 0000000..d57ed08 --- /dev/null +++ b/scripts/download_submodules.py @@ -0,0 +1,37 @@ +#!/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)() diff --git a/scripts/generate_third_party.py b/scripts/generate_third_party.py new file mode 100644 index 0000000..1713923 --- /dev/null +++ b/scripts/generate_third_party.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import argparse +import subprocess +from pathlib import Path +from shutil import rmtree +from urllib.parse import quote + +import call_wrapper + +def generate(): + cur_dir = Path(__file__).parent.absolute() + root_dir = cur_dir.parent + + output_file = root_dir/'THIRD_PARTY_NOTICES.md' + output_file.unlink(missing_ok=True) + + with open(root_dir/'.license_index.txt') as f: + index = [l.strip().split(': ') for l in f.readlines() if l.strip()] + + licenses = [f for f in (root_dir/'licenses').glob('*.txt')] + + if set([l[0] for l in index]) != set([l.stem for l in licenses]): + raise RuntimeError('License file mismatch:\n' + f'Index: {set([l for l in index[0]]) - set([l.stem for l in licenses])}\n' + f'Files: {set([l.stem for l in licenses]) - set([l for l in index[0]])}') + + with open(output_file, 'w') as output: + output.write('Spotify Integration uses third-party libraries or other resources that may\n') + output.write('be distributed under licenses different than the Spotify Integration software.\n') + output.write('') + output.write('The linked notices are provided for information only.\n') + output.write('\n') + for (dep_name, license) in index: + output.write(f'- [{dep_name} - {license}](licenses/{quote(dep_name)})\n') + + print(f'Generated file: {output_file}') + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Generate MD license file base on the folder with licenses') + + args = parser.parse_args() + + call_wrapper.final_call_decorator( + 'Generating MD license file', + 'Generating MD license file: success', + 'Generating MD license file: failure!' + )( + generate + )( + ) diff --git a/scripts/pack_component.py b/scripts/pack_component.py new file mode 100644 index 0000000..398ce88 --- /dev/null +++ b/scripts/pack_component.py @@ -0,0 +1,79 @@ +#!/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 + ) diff --git a/scripts/setup.py b/scripts/setup.py new file mode 100644 index 0000000..dffdd01 --- /dev/null +++ b/scripts/setup.py @@ -0,0 +1,87 @@ +#!/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 +import generate_third_party + +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('Commit hash header generation')( + load_module(scripts_path/'generate_commit_hash_header.py').generate_header_custom + )( + root_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 + )( + base_dir=root_dir, + output_dir=root_dir/'_result'/'AllPlatforms'/'generated', + repo='theqwertiest/foo_spotify' + ) + call_decorator('3rd-party notices generation')(generate_third_party.generate)() + +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 + ) diff --git a/submodules/acfu-sdk b/submodules/acfu-sdk new file mode 160000 index 0000000..edf59fb --- /dev/null +++ b/submodules/acfu-sdk @@ -0,0 +1 @@ +Subproject commit edf59fbc1b0cfb09f49c5b4c722008bd80f33b63 diff --git a/submodules/fb2k_utils b/submodules/fb2k_utils new file mode 160000 index 0000000..5af0440 --- /dev/null +++ b/submodules/fb2k_utils @@ -0,0 +1 @@ +Subproject commit 5af0440c64c3ee061b42d8a6888afc084635a5aa diff --git a/submodules/fmt b/submodules/fmt new file mode 160000 index 0000000..4b69c78 --- /dev/null +++ b/submodules/fmt @@ -0,0 +1 @@ +Subproject commit 4b69c78751488704c4dfdf03937112f179b0d4f4 diff --git a/submodules/foobar2000 b/submodules/foobar2000 new file mode 160000 index 0000000..8497799 --- /dev/null +++ b/submodules/foobar2000 @@ -0,0 +1 @@ +Subproject commit 84977992c69c00415c3b7c596448e9111587f3be diff --git a/submodules/json b/submodules/json new file mode 160000 index 0000000..350ff4f --- /dev/null +++ b/submodules/json @@ -0,0 +1 @@ +Subproject commit 350ff4f7ced7c4117eae2fb93df02823c8021fcb diff --git a/submodules/pfc b/submodules/pfc new file mode 160000 index 0000000..b4aa667 --- /dev/null +++ b/submodules/pfc @@ -0,0 +1 @@ +Subproject commit b4aa667dd3f5f91cc25d4fa849c80e6b9b6d886d diff --git a/submodules/range b/submodules/range new file mode 160000 index 0000000..0aed02e --- /dev/null +++ b/submodules/range @@ -0,0 +1 @@ +Subproject commit 0aed02e28cb672fae941d06be361b644646c3b78 diff --git a/submodules/span b/submodules/span new file mode 160000 index 0000000..6e08d34 --- /dev/null +++ b/submodules/span @@ -0,0 +1 @@ +Subproject commit 6e08d346f1a3980dd4c7e9d2a47894500ac071c3 diff --git a/submodules/wtl b/submodules/wtl new file mode 160000 index 0000000..bcbb6e5 --- /dev/null +++ b/submodules/wtl @@ -0,0 +1 @@ +Subproject commit bcbb6e5193a2f6b685703cb265f4b3090d73e31c diff --git a/workspaces/foo_spotify.sln b/workspaces/foo_spotify.sln new file mode 100644 index 0000000..8874694 --- /dev/null +++ b/workspaces/foo_spotify.sln @@ -0,0 +1,119 @@ + +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 +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(ExtensibilityGlobals) = postSolution + SolutionGuid = {869A994B-3306-4E73-A266-A14CF217581C} + EndGlobalSection +EndGlobal