Skip to content

Commit

Permalink
Add rustdoc to connect crate (librespot-org#1455)
Browse files Browse the repository at this point in the history
* restructure connect and add initial docs

* replace inline crate rustdoc with README.md

* connect: make metadata trait less visible

* connect: add some more docs

* chore: remove clippy warning

* update CHANGELOG.md

* revert unrelated changes

* enforce separation of autoplay and options

* hide and improve docs of uid

* remove/rename remarks

* update connect example and link in docs

* fixup rebase and clippy warnings
  • Loading branch information
photovoltex authored Feb 22, 2025
1 parent f497806 commit 09b4aa4
Show file tree
Hide file tree
Showing 14 changed files with 411 additions and 191 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ spotify_appkey.key
.vagrant/
.project
.history
.cache
*.save
*.*~
12 changes: 7 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- [core] MSRV is now 1.81 (breaking)
- [core] AP connect and handshake have a combined 5 second timeout.
- [connect] Replaced `ConnectConfig` with `ConnectStateConfig` (breaking)
- [connect] Replaced `playing_track_index` field of `SpircLoadCommand` with `playing_track` (breaking)
- [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)
- [connect] Moved all public items to the highest level (breaking)
- [connect] Replaced Mercury usage in `Spirc` with Dealer

### Added

- [connect] Add `seek_to` field to `SpircLoadCommand` (breaking)
- [connect] Add `repeat_track` field to `SpircLoadCommand` (breaking)
- [connect] Add `autoplay` field to `SpircLoadCommand` (breaking)
- [connect] Add support for `seek_to`, `repeat_track` and `autoplay` for `Spirc` loading
- [connect] Add `pause` parameter to `Spirc::disconnect` method (breaking)
- [connect] Add `volume_steps` to `ConnectConfig` (breaking)
- [connect] Add and enforce rustdoc
- [playback] Add `track` field to `PlayerEvent::RepeatChanged` (breaking)
- [core] Add `request_with_options` and `request_with_protobuf_and_options` to `SpClient`
- [oauth] Add `OAuthClient` and `OAuthClientBuilder` structs to achieve a more customizable login process
Expand Down
63 changes: 63 additions & 0 deletions connect/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[//]: # (This readme is optimized for inline rustdoc, if some links don't work, they will when included in lib.rs)

# Connect

The connect module of librespot. Provides the option to create your own connect device
and stream to it like any other official spotify client.

The [`Spirc`] is the entrypoint to creating your own connect device. It can be
configured with the given [`ConnectConfig`] options and requires some additional data
to start up the device.

When creating a new [`Spirc`] it returns two items. The [`Spirc`] itself, which is can
be used as to control the local connect device. And a [`Future`](std::future::Future),
lets name it `SpircTask`, that starts and executes the event loop of the connect device
when awaited.

A basic example in which the `Spirc` and `SpircTask` is used can be found here:
[`examples/play_connect.rs`](../examples/play_connect.rs).

# Example

```rust
use std::{future::Future, thread};

use librespot_connect::{ConnectConfig, Spirc};
use librespot_core::{authentication::Credentials, Error, Session, SessionConfig};
use librespot_playback::{
audio_backend, mixer,
config::{AudioFormat, PlayerConfig},
mixer::{MixerConfig, NoOpVolume},
player::Player
};

async fn create_basic_spirc() -> Result<(), Error> {
let credentials = Credentials::with_access_token("access-token-here");
let session = Session::new(SessionConfig::default(), None);

let backend = audio_backend::find(None).expect("will default to rodio");

let player = Player::new(
PlayerConfig::default(),
session.clone(),
Box::new(NoOpVolume),
move || {
let format = AudioFormat::default();
let device = None;
backend(device, format)
},
);

let mixer = mixer::find(None).expect("will default to SoftMixer");

let (spirc, spirc_task): (Spirc, _) = Spirc::new(
ConnectConfig::default(),
session,
credentials,
player,
mixer(MixerConfig::default())
).await?;

Ok(())
}
```
13 changes: 10 additions & 3 deletions connect/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#![warn(missing_docs)]
#![doc=include_str!("../README.md")]

#[macro_use]
extern crate log;

Expand All @@ -7,6 +10,10 @@ use librespot_protocol as protocol;

mod context_resolver;
mod model;
pub mod shuffle_vec;
pub mod spirc;
pub mod state;
mod shuffle_vec;
mod spirc;
mod state;

pub use model::*;
pub use spirc::*;
pub use state::*;
105 changes: 93 additions & 12 deletions connect/src/model.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,111 @@
use librespot_core::dealer::protocol::SkipTo;
use crate::{
core::dealer::protocol::SkipTo, protocol::context_player_options::ContextPlayerOptionOverrides,
};

use std::ops::Deref;

/// Request for loading playback
#[derive(Debug)]
pub struct SpircLoadCommand {
pub context_uri: String,
pub struct LoadRequest {
pub(super) context_uri: String,
pub(super) options: LoadRequestOptions,
}

impl Deref for LoadRequest {
type Target = LoadRequestOptions;

fn deref(&self) -> &Self::Target {
&self.options
}
}

/// The parameters for creating a load request
#[derive(Debug, Default)]
pub struct LoadRequestOptions {
/// Whether the given tracks should immediately start playing, or just be initially loaded.
pub start_playing: bool,
/// Start the playback at a specific point of the track.
///
/// The provided value is used as milliseconds. Providing a value greater
/// than the track duration will start the track at the beginning.
pub seek_to: u32,
pub shuffle: bool,
pub repeat: bool,
pub repeat_track: bool,
/// Decides if the context or the autoplay of the context is played
/// Options that decide how the context starts playing
pub context_options: Option<LoadContextOptions>,
/// Decides the starting position in the given context.
///
/// ## Remarks:
/// If `true` is provided, the option values (`shuffle`, `repeat` and `repeat_track`) are ignored
pub autoplay: bool,
/// Decides the starting position in the given context
/// If the provided item doesn't exist or is out of range,
/// the playback starts at the beginning of the context.
///
/// ## Remarks:
/// If `None` is provided and `shuffle` is `true`, a random track is played, otherwise the first
pub playing_track: Option<PlayingTrack>,
}

/// The options which decide how the playback is started
///
/// Separated into an `enum` to exclude the other variants from being used
/// simultaneously, as they are not compatible.
#[derive(Debug)]
pub enum LoadContextOptions {
/// Starts the context with options
Options(Options),
/// Starts the playback as the autoplay variant of the context
///
/// This is the same as finishing a context and
/// automatically continuing playback of similar tracks
Autoplay,
}

/// The available options that indicate how to start the context
#[derive(Debug, Default)]
pub struct Options {
/// Start the context in shuffle mode
pub shuffle: bool,
/// Start the context in repeat mode
pub repeat: bool,
/// Start the context, repeating the first track until skipped or manually disabled
pub repeat_track: bool,
}

impl From<ContextPlayerOptionOverrides> for Options {
fn from(value: ContextPlayerOptionOverrides) -> Self {
Self {
shuffle: value.shuffling_context.unwrap_or_default(),
repeat: value.repeating_context.unwrap_or_default(),
repeat_track: value.repeating_track.unwrap_or_default(),
}
}
}

impl LoadRequest {
/// Create a load request from a `context_uri`
///
/// For supported `context_uri` see [`SpClient::get_context`](librespot_core::spclient::SpClient::get_context)
pub fn from_context_uri(context_uri: String, options: LoadRequestOptions) -> Self {
Self {
context_uri,
options,
}
}
}

/// An item that represent a track to play
#[derive(Debug)]
pub enum PlayingTrack {
/// Represent the track at a given index.
Index(u32),
/// Represent the uri of a track.
Uri(String),
#[doc(hidden)]
/// Represent an internal identifier from spotify.
///
/// The internal identifier is not the id contained in the uri. And rather
/// an unrelated id probably unique in spotify's internal database. But that's
/// just speculation.
///
/// This identifier is not available by any public api. It's used for varies in
/// any spotify client, like sorting, displaying which track is currently played
/// and skipping to a track. Mobile uses it pretty intensively but also web and
/// desktop seem to make use of it.
Uid(String),
}

Expand Down
7 changes: 0 additions & 7 deletions connect/src/shuffle_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,6 @@ impl<T> From<Vec<T>> for ShuffleVec<T> {
}

impl<T> ShuffleVec<T> {
pub fn new() -> Self {
Self {
vec: Vec::new(),
indices: None,
}
}

pub fn shuffle_with_seed(&mut self, seed: u64) {
self.shuffle_with_rng(SmallRng::seed_from_u64(seed))
}
Expand Down
Loading

0 comments on commit 09b4aa4

Please sign in to comment.