1
0
Fork 0
mirror of https://github.com/librespot-org/librespot.git synced 2025-10-02 17:29:22 +02:00

feat: add configurable TLS backend selection with native-tls as default (#1541)

Add support for choosing between native-tls and rustls-tls backends
through feature flags, with native-tls as the default for maximum
platform compatibility.

Key changes:
- Add mutually exclusive native-tls and rustls-tls feature flags
- Use conditional compilation to select TLS implementation
- Configure rustls-tls with platform certificate verifier
- Refactor to workspace-based dependency management
- Update CI workflows with improved cross-compilation support
- Add comprehensive TLS backend documentation

The native-tls backend uses system TLS libraries (OpenSSL on Linux,
Secure Transport on macOS, SChannel on Windows) while rustls-tls
provides a pure Rust implementation with platform certificate stores.
This commit is contained in:
Roderick van Domburg 2025-08-19 23:06:28 +02:00 committed by GitHub
parent 03bcdc6bda
commit 0a4969ffe2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 928 additions and 577 deletions

View file

@ -8,7 +8,6 @@ ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL="sparse"
ENV RUST_BACKTRACE=1
ENV RUSTFLAGS="-D warnings"
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git \

View file

@ -7,7 +7,6 @@ ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL="sparse"
ENV RUST_BACKTRACE=1
ENV RUSTFLAGS="-D warnings -C target-feature=-crt-static"
RUN apk add --no-cache \
git \
nano\

View file

@ -1,24 +1,20 @@
{
"name": "Librespot Devcontainer",
"dockerFile": "Dockerfile.alpine",
// Use 'postCreateCommand' to run commands after the container is created.
//"postCreateCommand": "",
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
"settings": {
"dev.containers.copyGitConfig": true
},
"extensions": [
"eamodio.gitlens",
"github.vscode-github-actions",
"rust-lang.rust-analyzer"
]
}
},
"_postCreateCommand_comment": "Uncomment 'postCreateCommand' to run commands after the container is created.",
"_postCreateCommand": "",
"customizations": {
"_comment": "Configure properties specific to VS Code.",
"vscode": {
"settings": {
"dev.containers.copyGitConfig": true
},
"extensions": ["eamodio.gitlens", "github.vscode-github-actions", "rust-lang.rust-analyzer"]
}
},
"containerEnv": {
"GIT_EDITOR": "nano"
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
},
"_remoteUser_comment": "Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root",
"_remoteUser": "root"
}

99
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,99 @@
---
# Note, this is used in the badge URL!
name: build
"on":
push:
branches: [dev, master]
paths-ignore:
- "**.md"
- "docs/**"
- "contrib/**"
- "LICENSE"
- "*.sh"
- "**/Dockerfile*"
- "publish.sh"
- "test.sh"
pull_request:
paths-ignore:
- "**.md"
- "docs/**"
- "contrib/**"
- "LICENSE"
- "*.sh"
- "**/Dockerfile*"
- "publish.sh"
- "test.sh"
schedule:
# Run CI every week
- cron: "00 01 * * 0"
env:
RUST_BACKTRACE: 1
RUSTFLAGS: -D warnings
jobs:
test:
name: cargo +${{ matrix.toolchain }} test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
toolchain:
- "1.85" # MSRV (Minimum supported rust version)
- stable
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
- name: Install developer package dependencies (Linux)
if: runner.os == 'Linux'
run: >
sudo apt-get update && sudo apt-get install -y
libpulse-dev portaudio19-dev libasound2-dev libsdl2-dev
gstreamer1.0-dev libgstreamer-plugins-base1.0-dev
libavahi-compat-libdnssd-dev
- name: Fetch dependencies
run: cargo fetch --locked
- name: Build workspace with examples
run: cargo build --frozen --workspace --examples
- name: Run tests
run: cargo test --workspace
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
- name: Check packages without TLS requirements
run: cargo hack check -p librespot-protocol --each-feature
- name: Check workspace with native-tls
run: >
cargo hack check -p librespot --each-feature --exclude-all-features
--include-features native-tls --exclude-features rustls-tls
- name: Check workspace with rustls-tls
run: >
cargo hack check -p librespot --each-feature --exclude-all-features
--include-features rustls-tls --exclude-features native-tls
- name: Build binary with default features
run: cargo build --frozen
- name: Upload debug artifacts
uses: actions/upload-artifact@v4
with:
name: librespot-${{ matrix.os }}-${{ matrix.toolchain }}
path: >
target/debug/librespot${{ runner.os == 'Windows' && '.exe' || '' }}
if-no-files-found: error

78
.github/workflows/cross-compile.yml vendored Normal file
View file

@ -0,0 +1,78 @@
---
name: cross-compile
"on":
push:
branches: [dev, master]
paths-ignore:
- "**.md"
- "docs/**"
- "contrib/**"
- "LICENSE"
- "*.sh"
- "**/Dockerfile*"
pull_request:
paths-ignore:
- "**.md"
- "docs/**"
- "contrib/**"
- "LICENSE"
- "*.sh"
- "**/Dockerfile*"
env:
RUST_BACKTRACE: 1
RUSTFLAGS: -D warnings
jobs:
cross-compile:
name: cross +${{ matrix.toolchain }} build ${{ matrix.platform.target }}
runs-on: ${{ matrix.platform.runs-on }}
continue-on-error: false
strategy:
matrix:
platform:
- arch: armv7
runs-on: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
- arch: aarch64
runs-on: ubuntu-latest
target: aarch64-unknown-linux-gnu
- arch: riscv64gc
runs-on: ubuntu-latest
target: riscv64gc-unknown-linux-gnu
toolchain:
- "1.85" # MSRV (Minimum Supported Rust Version)
- stable
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Build binary with default features
if: matrix.platform.target != 'riscv64gc-unknown-linux-gnu'
uses: houseabsolute/actions-rust-cross@v1
with:
command: build
target: ${{ matrix.platform.target }}
toolchain: ${{ matrix.toolchain }}
args: --locked --verbose
- name: Build binary with rustls-tls and no default features
if: matrix.platform.target == 'riscv64gc-unknown-linux-gnu'
uses: houseabsolute/actions-rust-cross@v1
with:
command: build
target: ${{ matrix.platform.target }}
toolchain: ${{ matrix.toolchain }}
args: --locked --verbose --no-default-features --features rustls-tls
- name: Upload debug artifacts
uses: actions/upload-artifact@v4
with:
name: librespot-${{ matrix.platform.runs-on }}-${{ matrix.platform.arch }}-${{ matrix.toolchain }} # yamllint disable-line rule:line-length
path: target/${{ matrix.platform.target }}/debug/librespot
if-no-files-found: error

79
.github/workflows/quality.yml vendored Normal file
View file

@ -0,0 +1,79 @@
---
name: code-quality
"on":
push:
branches: [dev, master]
paths-ignore:
- "**.md"
- "docs/**"
- "contrib/**"
- "LICENSE"
- "*.sh"
- "**/Dockerfile*"
pull_request:
paths-ignore:
- "**.md"
- "docs/**"
- "contrib/**"
- "LICENSE"
- "*.sh"
- "**/Dockerfile*"
schedule:
# Run CI every week
- cron: "00 01 * * 0"
env:
RUST_BACKTRACE: 1
RUSTFLAGS: -D warnings
jobs:
fmt:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Check formatting
run: cargo fmt --all -- --check
clippy:
needs: fmt
name: cargo clippy
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
- name: Install developer package dependencies
run: >
sudo apt-get update && sudo apt-get install -y
libpulse-dev portaudio19-dev libasound2-dev libsdl2-dev
gstreamer1.0-dev libgstreamer-plugins-base1.0-dev
libavahi-compat-libdnssd-dev
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
- name: Run clippy on packages without TLS requirements
run: cargo hack clippy -p librespot-protocol --each-feature
- name: Run clippy with native-tls
run: >
cargo hack clippy -p librespot --each-feature --exclude-all-features
--include-features native-tls --exclude-features rustls-tls
- name: Run clippy with rustls-tls
run: >
cargo hack clippy -p librespot --each-feature --exclude-all-features
--include-features rustls-tls --exclude-features native-tls

View file

@ -1,281 +0,0 @@
# Note, this is used in the badge URL!
name: test
on:
push:
branches: [dev, master]
paths:
[
"**.rs",
"Cargo.toml",
"Cargo.lock",
"rustfmt.toml",
".github/workflows/*",
"!*.md",
"!contrib/*",
"!docs/*",
"!LICENSE",
"!*.sh",
]
pull_request:
paths:
[
"**.rs",
"Cargo.toml",
"Cargo.lock",
"rustfmt.toml",
".github/workflows/*",
"!*.md",
"!contrib/*",
"!docs/*",
"!LICENSE",
"!*.sh",
]
schedule:
# Run CI every week
- cron: "00 01 * * 0"
env:
RUST_BACKTRACE: 1
RUSTFLAGS: -D warnings
# The layering here is as follows:
# 1. code formatting
# 2. absence of lints
# 3. absence of errors and warnings on Linux/x86
# 4. cross compilation on Windows and Linux/ARM
jobs:
fmt:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install toolchain
run: curl https://sh.rustup.rs -sSf | sh -s -- --profile default --default-toolchain stable -y
- run: cargo fmt --all -- --check
clippy:
needs: fmt
name: cargo +${{ matrix.toolchain }} clippy (${{ matrix.os }})
runs-on: ${{ matrix.os }}
continue-on-error: false
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
toolchain: [stable]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install toolchain
run: curl https://sh.rustup.rs -sSf | sh -s -- --profile default --default-toolchain ${{ matrix.toolchain }} -y
- name: Get Rustc version
id: get-rustc-version
run: echo "version=$(rustc -V)" >> $GITHUB_OUTPUT
shell: bash
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git
target
key: ${{ runner.os }}-${{ steps.get-rustc-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
- name: Install developer package dependencies
run: sudo apt-get update && sudo apt install -y libunwind-dev && sudo apt-get install libpulse-dev portaudio19-dev libasound2-dev libsdl2-dev gstreamer1.0-dev libgstreamer-plugins-base1.0-dev libavahi-compat-libdnssd-dev
- run: cargo install cargo-hack
- run: cargo hack --workspace --remove-dev-deps
- run: cargo clippy -p librespot-core --no-default-features
- run: cargo clippy -p librespot-core
- run: cargo hack clippy --each-feature -p librespot-discovery
- run: cargo hack clippy --each-feature -p librespot-playback
- run: cargo hack clippy --each-feature
test-linux:
name: cargo +${{ matrix.toolchain }} check (${{ matrix.os }})
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
needs: clippy
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
toolchain:
- "1.85" # MSRV (Minimum supported rust version)
- stable
experimental: [false]
# Ignore failures in beta
include:
- os: ubuntu-latest
toolchain: beta
experimental: true
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install toolchain
run: curl https://sh.rustup.rs -sSf | sh -s -- --profile minimal --default-toolchain ${{ matrix.toolchain }} -y
- name: Get Rustc version
id: get-rustc-version
run: echo "version=$(rustc -V)" >> $GITHUB_OUTPUT
shell: bash
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git
target
key: ${{ runner.os }}-${{ steps.get-rustc-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
- name: Install developer package dependencies
run: sudo apt-get update && sudo apt install -y libunwind-dev && sudo apt-get install libpulse-dev portaudio19-dev libasound2-dev libsdl2-dev gstreamer1.0-dev libgstreamer-plugins-base1.0-dev libavahi-compat-libdnssd-dev
- run: cargo fetch --locked
- run: cargo build --frozen --workspace --examples
- run: cargo test --workspace
- run: cargo install cargo-hack
- run: cargo hack --workspace --remove-dev-deps
- run: cargo check -p librespot-core --no-default-features
- run: cargo check -p librespot-core
- run: cargo hack check --each-feature -p librespot-discovery
- run: cargo hack check --each-feature -p librespot-playback
- run: cargo hack check --each-feature
test-windows:
needs: clippy
name: cargo +${{ matrix.toolchain }} check (${{ matrix.os }})
runs-on: ${{ matrix.os }}
continue-on-error: false
strategy:
fail-fast: false
matrix:
os: [windows-latest]
toolchain:
- "1.85" # MSRV (Minimum supported rust version)
- stable
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install toolchain
run: curl https://sh.rustup.rs -sSf | sh -s -- --profile minimal --default-toolchain ${{ matrix.toolchain }} -y
- name: Get Rustc version
id: get-rustc-version
run: echo "version=$(rustc -V)" >> $GITHUB_OUTPUT
shell: bash
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git
target
key: ${{ runner.os }}-${{ steps.get-rustc-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
- run: cargo fetch --locked
- run: cargo build --frozen --workspace --examples
- run: cargo test --workspace
- run: cargo install cargo-hack
- run: cargo hack --workspace --remove-dev-deps
- run: cargo check --no-default-features
- run: cargo check
test-cross-linux:
name: cross +${{ matrix.toolchain }} build ${{ matrix.target }}
needs: clippy
runs-on: ${{ matrix.os }}
continue-on-error: false
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
target:
- armv7-unknown-linux-gnueabihf
- aarch64-unknown-linux-gnu
- riscv64gc-unknown-linux-gnu
toolchain:
- "1.85" # MSRV (Minimum supported rust version)
- stable
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install toolchain
run: curl https://sh.rustup.rs -sSf | sh -s -- --profile minimal --default-toolchain ${{ matrix.toolchain }} -y
- name: Get Rustc version
id: get-rustc-version
run: echo "version=$(rustc -V)" >> $GITHUB_OUTPUT
shell: bash
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git
target
key: ${{ runner.os }}-${{ matrix.target }}-${{ steps.get-rustc-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
- name: Install the cross compiler rust targets
run: rustup target add ${{ matrix.target }}
- name: Update and Install dependencies
run: sudo apt-get update && sudo apt-get install -y build-essential cmake libclang1
- name: Install bindgen-cli
run: cargo install --force --locked bindgen-cli
- name: Install cross compiler
run: |
if [ ${{ matrix.target }} = "armv7-unknown-linux-gnueabihf" ]; then
sudo apt-get install -y gcc-arm-linux-gnueabihf
fi
if [ ${{ matrix.target }} = "aarch64-unknown-linux-gnu" ]; then
sudo apt-get install -y gcc-aarch64-linux-gnu
fi
if [ ${{ matrix.target }} = "riscv64gc-unknown-linux-gnu" ]; then
sudo apt-get install -y gcc-riscv64-linux-gnu
fi
- name: Set target link compiler
run: |
# Convert target to uppercase and replace - with _
target=${{ matrix.target }}
target=${target^^}
target=${target//-/_}
if [ ${{ matrix.target }} = "armv7-unknown-linux-gnueabihf" ]; then
echo "CARGO_TARGET_${target}_LINKER=arm-linux-gnueabihf-gcc" >> $GITHUB_ENV
fi
if [ ${{ matrix.target }} = "aarch64-unknown-linux-gnu" ]; then
echo "CARGO_TARGET_${target}_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
fi
if [ ${{ matrix.target }} = "riscv64gc-unknown-linux-gnu" ]; then
echo "CARGO_TARGET_${target}_LINKER=riscv64-linux-gnu-gcc" >> $GITHUB_ENV
fi
- name: Fetch
run: cargo fetch --locked
- name: Build
run: cargo build --frozen --verbose --target ${{ matrix.target }} --no-default-features
- name: Check binary
run: file target/${{ matrix.target }}/debug/librespot

View file

@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [core] MSRV is now 1.85 with Rust edition 2024 (breaking)
- [core] AP connect and handshake have a combined 5 second timeout.
- [core] `stream_from_cdn` now accepts the URL as `TryInto<Uri>` instead of `CdnUrl` (breaking)
- [core] Moved from native TLS to ring and webpki on all platforms
- [core] Add TLS backend selection with native-tls and rustls-tls options, defaulting to native-tls
- [connect] Replaced `has_volume_ctrl` with `disable_volume` in `ConnectConfig` (breaking)
- [connect] Changed `initial_volume` from `Option<u16>` to `u16` in `ConnectConfig` (breaking)
- [connect] Replaced `SpircLoadCommand` with `LoadRequest`, `LoadRequestOptions` and `LoadContextOptions` (breaking)

View file

@ -67,6 +67,63 @@ Depending on the chosen backend, specific development libraries are required.
|dns_sd | `libavahi-compat-libdnssd-dev pkg-config` | `avahi-compat-libdns_sd-devel` | |
|libmdns (default) | | | |
### TLS library dependencies
librespot requires a TLS implementation for secure connections to Spotify's servers. You can choose between two mutually exclusive options:
#### native-tls (default)
Uses your system's native TLS implementation:
- **Linux**: OpenSSL
- **macOS**: Secure Transport (Security.framework)
- **Windows**: SChannel (Windows TLS)
This is the **default choice** and provides the best compatibility. It integrates with your system's certificate store and is well-tested across platforms.
**When to choose native-tls:**
- You want maximum compatibility
- You're using system-managed certificates
- You're on a standard Linux distribution with OpenSSL
- You're deploying on platforms where OpenSSL is already present
**Dependencies:**
On Debian/Ubuntu:
```shell
sudo apt-get install libssl-dev pkg-config
```
On Fedora:
```shell
sudo dnf install openssl-devel pkg-config
```
#### rustls-tls
Uses a Rust-based TLS implementation with `rustls-platform-verifier` for certificate authority (CA) verification:
- **Linux**: Uses system ca-certificates package
- **macOS**: Uses Security.framework for CA verification
- **Windows**: Uses Windows certificate store
**When to choose rustls-tls:**
- You want to avoid external OpenSSL dependencies
- You're building for reproducible/deterministic builds
- You're targeting platforms where OpenSSL is unavailable or problematic (musl, embedded, static linking)
- You're cross-compiling and want to avoid OpenSSL build complexity
- You prefer having cryptographic operations implemented in Rust
**No additional system dependencies required** - rustls is implemented in Rust (with some assembly for performance-critical cryptographic operations) and doesn't require external libraries like OpenSSL.
#### Building with specific TLS backends
```bash
# Default (native-tls)
cargo build
# Explicitly use native-tls
cargo build --no-default-features --features "native-tls rodio-backend with-libmdns"
# Use rustls-tls instead
cargo build --no-default-features --features "rustls-tls rodio-backend with-libmdns"
```
**Important:** The TLS backends are mutually exclusive. Attempting to enable both will result in a compile-time error.
### Getting the Source
The recommended method is to first fork the repo, so that you have a copy that you have read/write access to. After that, its a simple case of cloning your fork.
@ -95,19 +152,21 @@ cargo build --release
You will most likely want to build debug builds when developing, as they compile faster, and more verbose, and as the name suggests, are for the purposes of debugging. When submitting a bug report, it is recommended to use a debug build to capture stack traces.
There are also a number of compiler feature flags that you can add, in the event that you want to have certain additional features also compiled. The list of these is available on the [wiki](https://github.com/librespot-org/librespot/wiki/Compiling#addition-features).
There are also a number of compiler feature flags that you can add, in the event that you want to have certain additional features also compiled. All available features and their descriptions are documented in the main [Cargo.toml](Cargo.toml) file. Additional platform-specific information is available on the [wiki](https://github.com/librespot-org/librespot/wiki/Compiling#addition-features).
By default, librespot compiles with the ```rodio-backend``` and ```with-libmdns``` features. To compile without default features, you can run with:
By default, librespot compiles with the ```native-tls```, ```rodio-backend```, and ```with-libmdns``` features.
**Note:** librespot requires at least one TLS backend to function. Building with `--no-default-features` alone will fail compilation. For custom feature selection, you must specify at least one TLS backend along with your desired audio and discovery backends.
For example, to build with the ALSA audio, libmdns discovery, and native-tls backends:
```bash
cargo build --no-default-features
cargo build --no-default-features --features "native-tls alsa-backend with-libmdns"
```
Note that this will also disable zeroconf discovery backends for Spotify Connect. For normal use cases, select at least one audio and discovery backend.
For example, to build with the ALSA audio and libmdns discovery backend:
Or to use rustls-tls with ALSA:
```bash
cargo build --no-default-features --features "alsa-backend with-libmdns"
cargo build --no-default-features --features "rustls-tls alsa-backend with-libmdns"
```
### Running

212
Cargo.lock generated
View file

@ -167,9 +167,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.88"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
@ -369,6 +369,16 @@ dependencies = [
"libc",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -735,6 +745,21 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -1289,13 +1314,16 @@ dependencies = [
"http",
"hyper",
"hyper-rustls 0.26.0",
"hyper-tls",
"hyper-util",
"native-tls",
"pin-project-lite",
"rustls-native-certs 0.7.3",
"tokio",
"tokio-native-tls",
"tokio-rustls 0.25.0",
"tower-service",
"webpki",
"webpki-roots 0.26.11",
]
[[package]]
@ -1310,12 +1338,11 @@ dependencies = [
"hyper-util",
"log",
"rustls 0.22.4",
"rustls-native-certs",
"rustls-native-certs 0.7.3",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.25.0",
"tower-service",
"webpki-roots 0.26.11",
]
[[package]]
@ -1327,13 +1354,29 @@ dependencies = [
"http",
"hyper",
"hyper-util",
"log",
"rustls 0.23.31",
"rustls-pki-types",
"rustls-platform-verifier",
"tokio",
"tokio-rustls 0.26.2",
"tower-service",
"webpki-roots 1.0.2",
"webpki-roots",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
@ -1866,6 +1909,7 @@ dependencies = [
"hyper",
"hyper-proxy2",
"hyper-rustls 0.27.7",
"hyper-tls",
"hyper-util",
"librespot-oauth",
"librespot-protocol",
@ -1885,7 +1929,6 @@ dependencies = [
"rand 0.9.2",
"rand_distr",
"rsa",
"rustls 0.23.31",
"serde",
"serde_json",
"sha1",
@ -1970,7 +2013,6 @@ dependencies = [
"alsa",
"cpal",
"futures-util",
"glib",
"gstreamer",
"gstreamer-app",
"gstreamer-audio",
@ -2109,6 +2151,23 @@ dependencies = [
"serde",
]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework 2.11.1",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "ndk"
version = "0.9.0"
@ -2304,9 +2363,9 @@ dependencies = [
[[package]]
name = "objc2"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551"
checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc"
dependencies = [
"objc2-encode",
]
@ -2425,12 +2484,50 @@ dependencies = [
"pathdiff",
]
[[package]]
name = "openssl"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags 2.9.1",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "option-operations"
version = "0.5.0"
@ -2917,15 +3014,16 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls 0.27.7",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"native-tls",
"percent-encoding",
"pin-project-lite",
"quinn",
@ -2936,6 +3034,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-rustls 0.26.2",
"tower",
"tower-http",
@ -2944,7 +3043,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots 1.0.2",
"webpki-roots",
]
[[package]]
@ -3050,7 +3149,6 @@ version = "0.23.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
@ -3069,7 +3167,19 @@ dependencies = [
"rustls-pemfile",
"rustls-pki-types",
"schannel",
"security-framework",
"security-framework 2.11.1",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework 3.3.0",
]
[[package]]
@ -3091,6 +3201,33 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0"
dependencies = [
"core-foundation 0.10.1",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls 0.23.31",
"rustls-native-certs 0.8.1",
"rustls-platform-verifier-android",
"rustls-webpki 0.103.4",
"security-framework 3.3.0",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.52.0",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.102.8"
@ -3179,7 +3316,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.9.1",
"core-foundation",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c"
dependencies = [
"bitflags 2.9.1",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
@ -3548,7 +3698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags 2.9.1",
"core-foundation",
"core-foundation 0.9.4",
"system-configuration-sys",
]
@ -3734,6 +3884,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.25.0"
@ -3774,12 +3934,13 @@ checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1"
dependencies = [
"futures-util",
"log",
"native-tls",
"rustls 0.23.31",
"rustls-pki-types",
"tokio",
"tokio-native-tls",
"tokio-rustls 0.26.2",
"tungstenite",
"webpki-roots 0.26.11",
]
[[package]]
@ -3922,6 +4083,7 @@ dependencies = [
"http",
"httparse",
"log",
"native-tls",
"rand 0.9.2",
"rustls 0.23.31",
"rustls-pki-types",
@ -4006,6 +4168,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vergen"
version = "9.0.6"
@ -4198,12 +4366,12 @@ dependencies = [
]
[[package]]
name = "webpki-roots"
version = "0.26.11"
name = "webpki-root-certs"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
checksum = "4e4ffd8df1c57e87c325000a3d6ef93db75279dc3a231125aac571650f22b12a"
dependencies = [
"webpki-roots 1.0.2",
"rustls-pki-types",
]
[[package]]

View file

@ -1,16 +1,123 @@
[package]
name = "librespot"
version.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
description = "An open source client library for Spotify, with support for Spotify Connect"
keywords = ["audio", "spotify", "music", "streaming", "connect"]
categories = ["multimedia::audio"]
repository.workspace = true
readme = "README.md"
edition.workspace = true
include = [
"src/**/*",
"audio/**/*",
"connect/**/*",
"core/**/*",
"discovery/**/*",
"metadata/**/*",
"oauth/**/*",
"playback/**/*",
"protocol/**/*",
"Cargo.toml",
"README.md",
"LICENSE",
"COMPILING.md",
"CONTRIBUTING.md",
]
[workspace.package]
version = "0.6.0-dev"
rust-version = "1.85"
authors = ["Librespot Org"]
license = "MIT"
description = "An open source client library for Spotify, with support for Spotify Connect"
keywords = ["spotify"]
repository = "https://github.com/librespot-org/librespot"
readme = "README.md"
edition = "2024"
[workspace]
[features]
default = ["native-tls", "rodio-backend", "with-libmdns"]
# TLS backends (mutually exclusive - compile-time checks in oauth/src/lib.rs)
# Note: Feature validation is in oauth crate since it's compiled first in the dependency tree.
# See COMPILING.md for more details on TLS backend selection.
# native-tls: Uses the system's native TLS stack (OpenSSL on Linux, Secure Transport on macOS,
# SChannel on Windows). This is the default as it's well-tested, widely compatible, and integrates
# with system certificate stores. Choose this for maximum compatibility and when you want to use
# system-managed certificates.
native-tls = ["librespot-core/native-tls", "librespot-oauth/native-tls"]
# rustls-tls: Uses the Rust-based rustls TLS implementation with platform certificate verification.
# This provides a Rust TLS stack (with assembly optimizations) that uses rustls-platform-verifier to
# automatically select the appropriate certificate authority (CA) certificates from your system's
# trust store. Choose this for avoiding external OpenSSL dependencies, reproducible builds, or when
# targeting platforms where native TLS dependencies are unavailable or problematic (musl, embedded,
# static linking). On Linux it uses ca-certificates, on macOS it uses Security.framework, and on
# Windows it uses the Windows certificate store.
rustls-tls = ["librespot-core/rustls-tls", "librespot-oauth/rustls-tls"]
# Audio backends - see README.md for audio backend selection guide
# Cross-platform backends:
# rodio-backend: Cross-platform audio backend using Rodio (default). Provides good cross-platform
# compatibility with automatic backend selection. Uses ALSA on Linux, WASAPI on Windows, CoreAudio
# on macOS.
rodio-backend = ["librespot-playback/rodio-backend"]
# rodiojack-backend: Rodio backend with JACK support for professional audio setups.
rodiojack-backend = ["librespot-playback/rodiojack-backend"]
# gstreamer-backend: Uses GStreamer multimedia framework for audio output.
# Provides extensive audio processing capabilities.
gstreamer-backend = ["librespot-playback/gstreamer-backend"]
# portaudio-backend: Cross-platform audio I/O library backend.
portaudio-backend = ["librespot-playback/portaudio-backend"]
# sdl-backend: Simple DirectMedia Layer audio backend.
sdl-backend = ["librespot-playback/sdl-backend"]
# Platform-specific backends:
# alsa-backend: Advanced Linux Sound Architecture backend (Linux only).
# Provides low-latency audio output on Linux systems.
alsa-backend = ["librespot-playback/alsa-backend"]
# pulseaudio-backend: PulseAudio backend (Linux only).
# Integrates with the PulseAudio sound server for advanced audio routing.
pulseaudio-backend = ["librespot-playback/pulseaudio-backend"]
# jackaudio-backend: JACK Audio Connection Kit backend.
# Professional audio backend for low-latency, high-quality audio routing.
jackaudio-backend = ["librespot-playback/jackaudio-backend"]
# Network discovery backends - choose one for Spotify Connect device discovery
# See COMPILING.md for dependencies and platform support.
# with-libmdns: Pure-Rust mDNS implementation (default).
# No external dependencies, works on all platforms. Choose this for simple deployments or when
# avoiding system dependencies.
with-libmdns = ["librespot-discovery/with-libmdns"]
# with-avahi: Uses Avahi daemon for mDNS (Linux only).
# Integrates with system's Avahi service for network discovery. Choose this when you want to
# integrate with existing Avahi infrastructure or need advanced mDNS features. Requires
# libavahi-client-dev.
with-avahi = ["librespot-discovery/with-avahi"]
# with-dns-sd: Uses DNS Service Discovery (cross-platform).
# On macOS uses Bonjour, on Linux uses Avahi compatibility layer. Choose this for tight system
# integration on macOS or when using Avahi's dns-sd compatibility mode on Linux.
with-dns-sd = ["librespot-discovery/with-dns-sd"]
# Audio processing features:
# passthrough-decoder: Enables direct passthrough of Ogg Vorbis streams without decoding.
# Useful for custom audio processing pipelines or when you want to handle audio decoding
# externally. When enabled, audio is not decoded by librespot but passed through as raw Ogg Vorbis
# data.
passthrough-decoder = ["librespot-playback/passthrough-decoder"]
[lib]
name = "librespot"
@ -21,40 +128,26 @@ name = "librespot"
path = "src/main.rs"
doc = false
[dependencies.librespot-audio]
path = "audio"
version = "0.6.0-dev"
[dependencies.librespot-connect]
path = "connect"
version = "0.6.0-dev"
[dependencies.librespot-core]
path = "core"
version = "0.6.0-dev"
[dependencies.librespot-discovery]
path = "discovery"
version = "0.6.0-dev"
default-features = false
[dependencies.librespot-metadata]
path = "metadata"
version = "0.6.0-dev"
[dependencies.librespot-playback]
path = "playback"
version = "0.6.0-dev"
[dependencies.librespot-protocol]
path = "protocol"
version = "0.6.0-dev"
[dependencies.librespot-oauth]
path = "oauth"
version = "0.6.0-dev"
[workspace.dependencies]
librespot-audio = { path = "audio", default-features = false }
librespot-connect = { path = "connect", default-features = false }
librespot-core = { path = "core", default-features = false }
librespot-discovery = { path = "discovery", default-features = false }
librespot-metadata = { path = "metadata", default-features = false }
librespot-oauth = { path = "oauth", default-features = false }
librespot-playback = { path = "playback", default-features = false }
librespot-protocol = { path = "protocol", default-features = false }
[dependencies]
librespot-audio.workspace = true
librespot-connect.workspace = true
librespot-core.workspace = true
librespot-discovery.workspace = true
librespot-metadata.workspace = true
librespot-oauth.workspace = true
librespot-playback.workspace = true
librespot-protocol.workspace = true
data-encoding = "2.5"
env_logger = { version = "0.11.2", default-features = false, features = [
"color",
@ -77,53 +170,30 @@ tokio = { version = "1", features = [
] }
url = "2.2"
[features]
alsa-backend = ["librespot-playback/alsa-backend"]
portaudio-backend = ["librespot-playback/portaudio-backend"]
pulseaudio-backend = ["librespot-playback/pulseaudio-backend"]
jackaudio-backend = ["librespot-playback/jackaudio-backend"]
rodio-backend = ["librespot-playback/rodio-backend"]
rodiojack-backend = ["librespot-playback/rodiojack-backend"]
sdl-backend = ["librespot-playback/sdl-backend"]
gstreamer-backend = ["librespot-playback/gstreamer-backend"]
with-avahi = ["librespot-discovery/with-avahi"]
with-dns-sd = ["librespot-discovery/with-dns-sd"]
with-libmdns = ["librespot-discovery/with-libmdns"]
passthrough-decoder = ["librespot-playback/passthrough-decoder"]
default = ["rodio-backend", "with-libmdns"]
[package.metadata.deb]
maintainer = "librespot-org"
copyright = "2018 Paul Liétar"
maintainer = "Librespot Organization <noreply@github.com>"
copyright = "2015, Paul Liétar"
license-file = ["LICENSE", "4"]
depends = "$auto"
recommends = "avahi-daemon"
extended-description = """\
librespot is an open source client library for Spotify. It enables applications \
to use Spotify's service, without using the official but closed-source \
libspotify. Additionally, it will provide extra features which are not \
available in the official library."""
to use Spotify's service to control and play music via various backends, and to \
act as a Spotify Connect receiver. It is an alternative to the official and now \
deprecated closed-source libspotify. Additionally, it provides extra features \
which are not available in the official library.
.
This package provides the librespot binary for headless Spotify Connect playback. \
.
Note: librespot only works with Spotify Premium accounts."""
section = "sound"
priority = "optional"
assets = [
[
"target/release/librespot",
"usr/bin/",
"755",
],
[
"contrib/librespot.service",
"lib/systemd/system/",
"644",
],
[
"contrib/librespot.user.service",
"lib/systemd/user/",
"644",
],
# Main binary
["target/release/librespot", "usr/bin/", "755"],
# Documentation
["README.md", "usr/share/doc/librespot/", "644"],
# Systemd services
["contrib/librespot.service", "lib/systemd/system/", "644"],
["contrib/librespot.user.service", "lib/systemd/user/", "644"],
]
[workspace.package]
rust-version = "1.85"

12
Cross.toml Normal file
View file

@ -0,0 +1,12 @@
[build]
pre-build = [
"dpkg --add-architecture $CROSS_DEB_ARCH",
"apt-get update",
"apt-get --assume-yes install libssl-dev:$CROSS_DEB_ARCH libasound2-dev:$CROSS_DEB_ARCH",
]
[target.riscv64gc-unknown-linux-gnu]
# RISC-V: Uses rustls-tls (no system dependencies needed)
# Building with --no-default-features --features rustls-tls
# No pre-build steps required - rustls is pure Rust
pre-build = []

View file

@ -1,4 +1,4 @@
[![Build Status](https://github.com/librespot-org/librespot/workflows/test/badge.svg)](https://github.com/librespot-org/librespot/actions)
[![Build Status](https://github.com/librespot-org/librespot/workflows/build/badge.svg)](https://github.com/librespot-org/librespot/actions)
[![Gitter chat](https://badges.gitter.im/librespot-org/librespot.png)](https://gitter.im/librespot-org/spotify-connect-resources)
[![Crates.io](https://img.shields.io/crates/v/librespot.svg)](https://crates.io/crates/librespot)
@ -62,13 +62,15 @@ SDL
Pipe
Subprocess
```
Please check the corresponding [Compiling](https://github.com/librespot-org/librespot/wiki/Compiling#general-dependencies) entry on the wiki for backend specific dependencies.
Please check [COMPILING.md](COMPILING.md) for detailed information on TLS, audio, and discovery backend dependencies, or the [Compiling](https://github.com/librespot-org/librespot/wiki/Compiling#general-dependencies) entry on the wiki for additional backend specific dependencies.
Once you've installed the dependencies and cloned this repository you can build *librespot* with the default backend using Cargo.
Once you've installed the dependencies and cloned this repository you can build *librespot* with the default features using Cargo.
```shell
cargo build --release
```
By default, this builds with native-tls (system TLS), rodio audio backend, and libmdns discovery. See [COMPILING.md](COMPILING.md) for information on selecting different TLS, audio, and discovery backends.
# Packages
librespot is also available via official package system on various operating systems such as Linux, FreeBSD, NetBSD. [Repology](https://repology.org/project/librespot/versions) offers a good overview.
@ -115,6 +117,6 @@ This is a non exhaustive list of projects that either use or have modified libre
- [librespot-java](https://github.com/devgianlu/librespot-java) - A Java port of librespot.
- [ncspot](https://github.com/hrkfdn/ncspot) - Cross-platform ncurses Spotify client.
- [ansible-role-librespot](https://github.com/xMordax/ansible-role-librespot/tree/master) - Ansible role that will build, install and configure Librespot.
- [Spot](https://github.com/xou816/spot) - Gtk/Rust native Spotify client for the GNOME desktop.
- [Spot](https://github.com/xou816/spot) - Gtk/Rust native Spotify client for the GNOME desktop.
- [Snapcast](https://github.com/badaix/snapcast) - synchronised multi-room audio player that uses librespot as its source for Spotify content
- [MuPiBox](https://mupibox.de/) - Portable music box for Spotify and local media based on Raspberry Pi. Operated via touchscreen. Suitable for children and older people.

View file

@ -1,25 +1,31 @@
[package]
name = "librespot-audio"
version = "0.6.0-dev"
version.workspace = true
rust-version.workspace = true
authors = ["Paul Lietar <paul@lietar.net>"]
license.workspace = true
description = "The audio fetching logic for librespot"
license = "MIT"
repository = "https://github.com/librespot-org/librespot"
edition = "2021"
repository.workspace = true
edition.workspace = true
[dependencies.librespot-core]
path = "../core"
version = "0.6.0-dev"
[features]
# Refer to the workspace Cargo.toml for the list of features
default = ["native-tls"]
# TLS backend propagation
native-tls = ["librespot-core/native-tls"]
rustls-tls = ["librespot-core/rustls-tls"]
[dependencies]
librespot-core.workspace = true
aes = "0.8"
bytes = "1"
ctr = "0.9"
futures-util = "0.3"
hyper = "1.6"
hyper-util = { version = "0.1", features = ["client"] }
http-body-util = "0.1"
hyper = { version = "1.6", features = ["http1", "http2"] }
hyper-util = { version = "0.1", features = ["client", "http2"] }
log = "0.4"
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
tempfile = "3"

View file

@ -162,7 +162,7 @@ impl StreamLoaderController {
}
pub fn range_available(&self, range: Range) -> bool {
let available = if let Some(ref shared) = self.stream_shared {
if let Some(ref shared) = self.stream_shared {
let download_status = shared.download_status.lock();
range.length
@ -171,9 +171,7 @@ impl StreamLoaderController {
.contained_length_from_value(range.start)
} else {
range.length <= self.len() - range.start
};
available
}
}
pub fn range_to_end_available(&self) -> bool {
@ -397,12 +395,12 @@ impl AudioFile {
pub fn get_stream_loader_controller(&self) -> Result<StreamLoaderController, Error> {
let controller = match self {
AudioFile::Streaming(ref stream) => StreamLoaderController {
AudioFile::Streaming(stream) => StreamLoaderController {
channel_tx: Some(stream.stream_loader_command_tx.clone()),
stream_shared: Some(stream.shared.clone()),
file_size: stream.shared.file_size,
},
AudioFile::Cached(ref file) => StreamLoaderController {
AudioFile::Cached(file) => StreamLoaderController {
channel_tx: None,
stream_shared: None,
file_size: file.metadata()?.len() as usize,

View file

@ -1,14 +1,26 @@
[package]
name = "librespot-connect"
version = "0.6.0-dev"
version.workspace = true
rust-version.workspace = true
authors = ["Paul Lietar <paul@lietar.net>"]
description = "The discovery and Spotify Connect logic for librespot"
license = "MIT"
repository = "https://github.com/librespot-org/librespot"
edition = "2021"
license.workspace = true
description = "The Spotify Connect logic for librespot"
repository.workspace = true
edition.workspace = true
[features]
# Refer to the workspace Cargo.toml for the list of features
default = ["native-tls"]
# TLS backend propagation
native-tls = ["librespot-core/native-tls", "librespot-playback/native-tls"]
rustls-tls = ["librespot-core/rustls-tls", "librespot-playback/rustls-tls"]
[dependencies]
librespot-core.workspace = true
librespot-playback.workspace = true
librespot-protocol.workspace = true
futures-util = "0.3"
log = "0.4"
protobuf = "3.7"
@ -18,15 +30,3 @@ thiserror = "2"
tokio = { version = "1", features = ["macros", "parking_lot", "sync"] }
tokio-stream = "0.1"
uuid = { version = "1.18", features = ["v4"] }
[dependencies.librespot-core]
path = "../core"
version = "0.6.0-dev"
[dependencies.librespot-playback]
path = "../playback"
version = "0.6.0-dev"
[dependencies.librespot-protocol]
path = "../protocol"
version = "0.6.0-dev"

View file

@ -1,27 +1,43 @@
[package]
name = "librespot-core"
version = "0.6.0-dev"
version.workspace = true
rust-version.workspace = true
authors = ["Paul Lietar <paul@lietar.net>"]
build = "build.rs"
license.workspace = true
description = "The core functionality provided by librespot"
license = "MIT"
repository = "https://github.com/librespot-org/librespot"
edition = "2021"
repository.workspace = true
edition.workspace = true
build = "build.rs"
[dependencies.librespot-oauth]
path = "../oauth"
version = "0.6.0-dev"
[features]
# Refer to the workspace Cargo.toml for the list of features
default = ["native-tls"]
[dependencies.librespot-protocol]
path = "../protocol"
version = "0.6.0-dev"
# TLS backends (mutually exclusive - see oauth/src/lib.rs for compile-time checks)
# Note: Validation is in oauth since it's compiled first in the dependency tree.
native-tls = [
"dep:hyper-tls",
"hyper-proxy2/tls",
"librespot-oauth/native-tls",
"tokio-tungstenite/native-tls",
]
rustls-tls = [
"dep:hyper-rustls",
"hyper-proxy2/rustls",
"librespot-oauth/rustls-tls",
"tokio-tungstenite/__rustls-tls",
]
[dependencies]
librespot-oauth.workspace = true
librespot-protocol.workspace = true
aes = "0.8"
base64 = "0.22"
byteorder = "1.5"
bytes = "1"
data-encoding = "2.9"
flate2 = "1.1"
form_urlencoded = "1.2"
futures-core = "0.3"
futures-util = { version = "0.3", features = [
@ -37,9 +53,22 @@ governor = { version = "0.10", default-features = false, features = [
hmac = "0.12"
httparse = "1.10"
http = "1.3"
hyper = { version = "1.6", features = ["http1", "http2"] }
hyper-util = { version = "0.1", features = ["client"] }
http-body-util = "0.1"
hyper = { version = "1.6", features = ["http1", "http2"] }
hyper-proxy2 = { version = "0.1", default-features = false }
hyper-rustls = { version = "0.27", default-features = false, features = [
"http1",
"http2",
"ring",
"rustls-platform-verifier",
"tls12",
], optional = true }
hyper-tls = { version = "0.6", optional = true }
hyper-util = { version = "0.1", default-features = false, features = [
"client",
"http1",
"http2",
] }
log = "0.4"
nonzero_ext = "0.3"
num-bigint = "0.4"
@ -51,6 +80,7 @@ pbkdf2 = { version = "0.12", default-features = false, features = ["hmac"] }
pin-project-lite = "0.2"
priority-queue = "2.5"
protobuf = "3.7"
protobuf-json-mapping = "3.7"
quick-xml = { version = "0.38", features = ["serialize"] }
rand = "0.9"
rsa = "0.9"
@ -71,28 +101,10 @@ tokio = { version = "1", features = [
"time",
] }
tokio-stream = "0.1"
tokio-tungstenite = { version = "0.27", default-features = false }
tokio-util = { version = "0.7", features = ["codec"] }
url = "2"
uuid = { version = "1", default-features = false, features = ["v4"] }
data-encoding = "2.9"
flate2 = "1.1"
protobuf-json-mapping = "3.7"
rustls = { version = "0.23", default-features = false, features = ["ring"] }
hyper-proxy2 = { version = "0.1", default-features = false, features = [
"rustls-webpki",
] }
hyper-rustls = { version = "0.27", default-features = false, features = [
"ring",
"http1",
"logging",
"tls12",
"webpki-tokio",
"http2",
] }
tokio-tungstenite = { version = "0.27", default-features = false, features = [
"rustls-tls-webpki-roots",
] }
[build-dependencies]
rand = "0.9"

View file

@ -13,7 +13,6 @@ use http::{Uri, header::HeaderValue};
use http_body_util::{BodyExt, Full};
use hyper::{HeaderMap, Request, Response, StatusCode, body::Incoming, header::USER_AGENT};
use hyper_proxy2::{Intercept, Proxy, ProxyConnector};
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
use hyper_util::{
client::legacy::{Client, ResponseFuture, connect::HttpConnector},
rt::TokioExecutor,
@ -23,6 +22,11 @@ use parking_lot::Mutex;
use thiserror::Error;
use url::Url;
#[cfg(all(feature = "rustls-tls", not(feature = "native-tls")))]
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
#[cfg(all(feature = "native-tls", not(feature = "rustls-tls")))]
use hyper_tls::HttpsConnector;
use crate::{
Error,
config::{OS, os_version},
@ -145,14 +149,15 @@ impl HttpClient {
fn try_create_hyper_client(proxy_url: Option<&Url>) -> Result<HyperClient, Error> {
// configuring TLS is expensive and should be done once per process
let _ = rustls::crypto::ring::default_provider()
.install_default()
.map_err(|e| {
Error::internal(format!("unable to install default crypto provider: {e:?}"))
});
let tls = HttpsConnectorBuilder::new().with_webpki_roots();
let https_connector = tls.https_or_http().enable_http1().enable_http2().build();
#[cfg(all(feature = "rustls-tls", not(feature = "native-tls")))]
let https_connector = {
let tls = HttpsConnectorBuilder::new().with_platform_verifier();
tls.https_or_http().enable_http1().enable_http2().build()
};
#[cfg(all(feature = "native-tls", not(feature = "rustls-tls")))]
let https_connector = HttpsConnector::new();
// When not using a proxy a dummy proxy is configured that will not intercept any traffic.
// This prevents needing to carry the Client Connector generics through the whole project

View file

@ -1,14 +1,29 @@
[package]
name = "librespot-discovery"
version = "0.6.0-dev"
version.workspace = true
rust-version.workspace = true
authors = ["Paul Lietar <paul@lietar.net>"]
license.workspace = true
description = "The discovery logic for librespot"
license = "MIT"
repository = "https://github.com/librespot-org/librespot"
edition = "2021"
repository.workspace = true
edition.workspace = true
[features]
# Refer to the workspace Cargo.toml for the list of features
default = ["with-libmdns", "native-tls"]
# Discovery backends
with-avahi = ["dep:serde", "dep:zbus"]
with-dns-sd = ["dep:dns-sd"]
with-libmdns = ["dep:libmdns"]
# TLS backend propagation
native-tls = ["librespot-core/native-tls"]
rustls-tls = ["librespot-core/rustls-tls"]
[dependencies]
librespot-core.workspace = true
aes = "0.8"
base64 = "0.22"
bytes = "1"
@ -18,13 +33,13 @@ form_urlencoded = "1.2"
futures-core = "0.3"
futures-util = "0.3"
hmac = "0.12"
http-body-util = "0.1"
hyper = { version = "1.6", features = ["http1"] }
hyper-util = { version = "0.1", features = [
"server-auto",
"server-graceful",
"service",
] }
http-body-util = "0.1"
libmdns = { version = "0.9", optional = true }
log = "0.4"
rand = "0.9"
@ -40,18 +55,7 @@ zbus = { version = "5", default-features = false, features = [
"tokio",
], optional = true }
[dependencies.librespot-core]
path = "../core"
version = "0.6.0-dev"
[dev-dependencies]
futures = "0.3"
hex = "0.4"
tokio = { version = "1", features = ["macros", "parking_lot", "rt"] }
[features]
with-avahi = ["zbus", "serde"]
with-dns-sd = ["dns-sd"]
with-libmdns = ["libmdns"]
default = ["with-libmdns"]

View file

@ -16,6 +16,6 @@ async fn main() {
.unwrap();
while let Some(x) = server.next().await {
println!("Received {:?}", x);
println!("Received {x:?}");
}
}

View file

@ -17,6 +17,6 @@ async fn main() {
.unwrap();
while let Some(x) = server.next().await {
println!("Received {:?}", x);
println!("Received {x:?}");
}
}

View file

@ -1,27 +1,30 @@
[package]
name = "librespot-metadata"
version = "0.6.0-dev"
version.workspace = true
rust-version.workspace = true
authors = ["Paul Lietar <paul@lietar.net>"]
license.workspace = true
description = "The metadata logic for librespot"
license = "MIT"
repository = "https://github.com/librespot-org/librespot"
edition = "2021"
repository.workspace = true
edition.workspace = true
[features]
# Refer to the workspace Cargo.toml for the list of features
default = ["native-tls"]
# TLS backend propagation
native-tls = ["librespot-core/native-tls"]
rustls-tls = ["librespot-core/rustls-tls"]
[dependencies]
librespot-core.workspace = true
librespot-protocol.workspace = true
async-trait = "0.1"
bytes = "1"
log = "0.4"
protobuf = "3.7"
thiserror = "2"
uuid = { version = "1", default-features = false }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[dependencies.librespot-core]
path = "../core"
version = "0.6.0-dev"
[dependencies.librespot-protocol]
path = "../protocol"
version = "0.6.0-dev"
thiserror = "2"
uuid = { version = "1", default-features = false }

View file

@ -50,7 +50,7 @@ pub trait Metadata: Send + Sized + 'static {
async fn get(session: &Session, id: &SpotifyId) -> Result<Self, Error> {
let response = Self::request(session, id).await?;
let msg = Self::Message::parse_from_bytes(&response)?;
trace!("Received metadata: {:#?}", msg);
trace!("Received metadata: {msg:#?}");
Self::parse(&msg, id)
}

View file

@ -21,7 +21,7 @@ pub trait MercuryRequest {
let _ = write!(metrics_uri, "&product={product}");
}
trace!("Requesting {}", metrics_uri);
trace!("Requesting {metrics_uri}");
let request = session.mercury().get(metrics_uri)?;
let response = request.await?;

View file

@ -1,23 +1,31 @@
[package]
name = "librespot-oauth"
version = "0.6.0-dev"
version.workspace = true
rust-version.workspace = true
authors = ["Nick Steel <nick@nsteel.co.uk>"]
license.workspace = true
description = "OAuth authorization code flow with PKCE for obtaining a Spotify access token"
license = "MIT"
repository = "https://github.com/librespot-org/librespot"
edition = "2021"
repository.workspace = true
edition.workspace = true
[features]
# Refer to the workspace Cargo.toml for the list of features
default = ["native-tls"]
# TLS backends (mutually exclusive - compile-time checks in src/lib.rs)
native-tls = ["oauth2/native-tls", "reqwest/native-tls"]
rustls-tls = ["oauth2/rustls-tls", "reqwest/rustls-tls"]
[dependencies]
log = "0.4"
oauth2 = { version = "5.0", features = ["reqwest", "reqwest-blocking"] }
reqwest = { version = "0.12", default-features = false, features = [
"blocking",
"http2",
"rustls-tls",
"system-proxy",
oauth2 = { version = "5.0", default-features = false, features = [
"reqwest",
"reqwest-blocking",
] }
open = "5.3"
reqwest = { version = "0.12", default-features = false, features = [
"system-proxy",
] }
thiserror = "2"
url = "2.5"

View file

@ -42,7 +42,7 @@ async fn main() {
{
Ok(client) => client,
Err(err) => {
eprintln!("Unable to build an OAuth client: {}", err);
eprintln!("Unable to build an OAuth client: {err}");
return;
}
};

View file

@ -41,7 +41,7 @@ fn main() {
{
Ok(client) => client,
Err(err) => {
eprintln!("Unable to build an OAuth client: {}", err);
eprintln!("Unable to build an OAuth client: {err}");
return;
}
};

View file

@ -11,23 +11,42 @@
//! a spawned http server (mimicking Spotify's client), or manually via stdin. The latter
//! is appropriate for headless systems.
use log::{error, info, trace};
use oauth2::basic::BasicTokenType;
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, CsrfToken, EndpointNotSet, EndpointSet,
PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, TokenUrl, basic::BasicClient,
};
use oauth2::{EmptyExtraTokenFields, PkceCodeVerifier, RefreshToken, StandardTokenResponse};
use std::io;
use std::sync::mpsc;
use std::time::{Duration, Instant};
use std::{
io::{BufRead, BufReader, Write},
io::{self, BufRead, BufReader, Write},
net::{SocketAddr, TcpListener},
sync::mpsc,
time::{Duration, Instant},
};
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, CsrfToken, EmptyExtraTokenFields, EndpointNotSet,
EndpointSet, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken, Scope,
StandardTokenResponse, TokenResponse, TokenUrl, basic::BasicClient, basic::BasicTokenType,
};
use log::{error, info, trace};
use thiserror::Error;
use url::Url;
// TLS Feature Validation
//
// These compile-time checks are placed in the oauth crate rather than core for a specific reason:
// oauth is at the bottom of the dependency tree (even librespot-core depends on librespot-oauth),
// which means it gets compiled first. This ensures TLS feature conflicts are detected early in
// the build process, providing immediate feedback to users rather than failing later during
// core compilation.
//
// The dependency chain is: workspace -> core -> oauth
// So oauth's feature validation runs before core's, catching configuration errors quickly.
#[cfg(all(feature = "native-tls", feature = "rustls-tls"))]
compile_error!("Features 'native-tls' and 'rustls-tls' are mutually exclusive. Enable only one.");
#[cfg(not(any(feature = "native-tls", feature = "rustls-tls")))]
compile_error!(
"Either feature \"native-tls\" (default) or \"rustls-tls\" must be enabled for this crate."
);
/// Possible errors encountered during the OAuth authentication flow.
#[derive(Debug, Error)]
pub enum OAuthError {

View file

@ -1,26 +1,51 @@
[package]
name = "librespot-playback"
version = "0.6.0-dev"
version.workspace = true
rust-version.workspace = true
authors = ["Sasha Hilton <sashahilton00@gmail.com>"]
license.workspace = true
description = "The audio playback logic for librespot"
license = "MIT"
repository = "https://github.com/librespot-org/librespot"
edition = "2021"
repository.workspace = true
edition.workspace = true
[dependencies.librespot-audio]
path = "../audio"
version = "0.6.0-dev"
[features]
# Refer to the workspace Cargo.toml for the list of features
default = ["rodio-backend", "native-tls"]
[dependencies.librespot-core]
path = "../core"
version = "0.6.0-dev"
# Audio backends
alsa-backend = ["dep:alsa"]
gstreamer-backend = [
"dep:gstreamer",
"dep:gstreamer-app",
"dep:gstreamer-audio",
]
jackaudio-backend = ["dep:jack"]
portaudio-backend = ["dep:portaudio-rs"]
pulseaudio-backend = ["dep:libpulse-binding", "dep:libpulse-simple-binding"]
rodio-backend = ["dep:cpal", "dep:rodio"]
rodiojack-backend = ["dep:rodio", "cpal/jack"]
sdl-backend = ["dep:sdl2"]
[dependencies.librespot-metadata]
path = "../metadata"
version = "0.6.0-dev"
# Audio processing features
passthrough-decoder = ["dep:ogg"]
# TLS backend propagation
native-tls = [
"librespot-core/native-tls",
"librespot-audio/native-tls",
"librespot-metadata/native-tls",
]
rustls-tls = [
"librespot-core/rustls-tls",
"librespot-audio/rustls-tls",
"librespot-metadata/rustls-tls",
]
[dependencies]
librespot-audio.workspace = true
librespot-core.workspace = true
librespot-metadata.workspace = true
portable-atomic = "1"
futures-util = "0.3"
log = "0.4"
@ -37,21 +62,24 @@ zerocopy = { version = "0.8", features = ["derive"] }
# Backends
alsa = { version = "0.9", optional = true }
portaudio-rs = { version = "0.3", optional = true }
libpulse-binding = { version = "2", optional = true, default-features = false }
libpulse-simple-binding = { version = "2", optional = true, default-features = false }
jack = { version = "0.13", optional = true }
portaudio-rs = { version = "0.3", optional = true }
sdl2 = { version = "0.38", optional = true }
# GStreamer dependencies
gstreamer = { version = "0.24", optional = true }
gstreamer-app = { version = "0.24", optional = true }
gstreamer-audio = { version = "0.24", optional = true }
glib = { version = "0.21", optional = true }
# PulseAudio dependencies
libpulse-binding = { version = "2", optional = true, default-features = false }
libpulse-simple-binding = { version = "2", optional = true, default-features = false }
# Rodio dependencies
cpal = { version = "0.16", optional = true }
rodio = { version = "0.21", optional = true, default-features = false, features = [
"playback",
] }
cpal = { version = "0.16", optional = true }
# Container and audio decoder
symphonia = { version = "0.5", default-features = false, features = [
@ -66,15 +94,3 @@ ogg = { version = "0.9", optional = true }
# Dithering
rand = { version = "0.9", features = ["small_rng"] }
rand_distr = "0.5"
[features]
alsa-backend = ["alsa"]
portaudio-backend = ["portaudio-rs"]
pulseaudio-backend = ["libpulse-binding", "libpulse-simple-binding"]
jackaudio-backend = ["jack"]
rodio-backend = ["rodio", "cpal"]
rodiojack-backend = ["rodio", "cpal/jack"]
sdl-backend = ["sdl2"]
gstreamer-backend = ["gstreamer", "gstreamer-app", "gstreamer-audio"]
passthrough-decoder = ["ogg"]

View file

@ -81,7 +81,7 @@ impl MappedCtrl for VolumeCtrl {
fn set_db_range(&mut self, new_db_range: f64) {
match self {
Self::Cubic(ref mut db_range) | Self::Log(ref mut db_range) => *db_range = new_db_range,
Self::Cubic(db_range) | Self::Log(db_range) => *db_range = new_db_range,
_ => error!("Invalid to set dB range for volume control type {self:?}"),
}

View file

@ -1,13 +1,13 @@
[package]
name = "librespot-protocol"
version = "0.6.0-dev"
version.workspace = true
rust-version.workspace = true
authors = ["Paul Liétar <paul@lietar.net>"]
build = "build.rs"
license.workspace = true
description = "The protobuf logic for communicating with Spotify servers"
license = "MIT"
repository = "https://github.com/librespot-org/librespot"
edition = "2021"
repository.workspace = true
edition.workspace = true
build = "build.rs"
[dependencies]
protobuf = "3.7"