mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
2708aa4fef
13 changed files with 220 additions and 77 deletions
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
@ -0,0 +1 @@
|
|||
target
|
|
@ -16,6 +16,7 @@ path = "src/lib.rs"
|
|||
[[bin]]
|
||||
name = "librespot"
|
||||
path = "src/main.rs"
|
||||
doc = false
|
||||
|
||||
[dependencies.librespot-protocol]
|
||||
path = "protocol"
|
||||
|
@ -86,5 +87,5 @@ section = "sound"
|
|||
priority = "optional"
|
||||
assets = [
|
||||
["target/release/librespot", "usr/bin/", "755"],
|
||||
["assets/librespot.service", "lib/systemd/system/", "644"]
|
||||
["contrib/librespot.service", "lib/systemd/system/", "644"]
|
||||
]
|
||||
|
|
34
README.md
34
README.md
|
@ -4,10 +4,6 @@ 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.
|
||||
|
||||
## Status
|
||||
*librespot* is currently under development and is not fully functional yet. You
|
||||
are however welcome to experiment with it.
|
||||
|
||||
## Building
|
||||
Rust 1.7.0 or later is required to build librespot.
|
||||
|
||||
|
@ -60,6 +56,36 @@ The following backends are currently available :
|
|||
- PortAudio
|
||||
- PulseAudio
|
||||
|
||||
## Cross-compiling
|
||||
A cross compilation environment is provided as a docker image.
|
||||
Build the image from the root of the project with the following command :
|
||||
|
||||
```
|
||||
$ docker build -t librespot-cross -f contrib/Dockerfile .
|
||||
```
|
||||
|
||||
The resulting image can be used to build librespot for linux x86_64, armhf and armel.
|
||||
The compiled binaries will be located in /tmp/librespot-build
|
||||
|
||||
```
|
||||
docker run -v /tmp/librespot-build:/build librespot-cross
|
||||
```
|
||||
|
||||
If only one architecture is desired, cargo can be invoked directly with the appropriate options :
|
||||
```shell
|
||||
docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --no-default-features --features "with-syntex alsa-backend"
|
||||
docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --target arm-unknown-linux-gnueabihf --no-default-features --features "with-syntex alsa-backend"
|
||||
docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --target arm-unknown-linux-gnueabi --no-default-features --features "with-syntex alsa-backend"
|
||||
```
|
||||
|
||||
## Development
|
||||
When developing *librespot*, it is preferable to use Rust nightly, and build it using the following :
|
||||
```shell
|
||||
cargo build --no-default-features --features "nightly portaudio-backend"
|
||||
```
|
||||
|
||||
This produces better compilation error messages than with the default configuration.
|
||||
|
||||
## Disclaimer
|
||||
Using this code to connect to Spotify's API is probably forbidden by them.
|
||||
Use at your own risk.
|
||||
|
|
40
contrib/Dockerfile
Normal file
40
contrib/Dockerfile
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Cross compilation environment for librespot
|
||||
# Build the docker image from the root of the project with the following command :
|
||||
# $ docker build -t librespot-cross -f contrib/Dockerfile .
|
||||
#
|
||||
# The resulting image can be used to build librespot for linux x86_64, armhf and armel.
|
||||
# $ docker run -v /tmp/librespot-build:/build librespot-cross
|
||||
#
|
||||
# The compiled binaries will be located in /tmp/librespot-build
|
||||
#
|
||||
# If only one architecture is desired, cargo can be invoked directly with the appropriate options :
|
||||
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --no-default-features --features "with-syntex alsa-backend"
|
||||
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --target arm-unknown-linux-gnueabihf --no-default-features --features "with-syntex alsa-backend"
|
||||
# $ docker run -v /tmp/librespot-build:/build librespot-cross cargo build --release --target arm-unknown-linux-gnueabi --no-default-features --features "with-syntex alsa-backend"
|
||||
#
|
||||
|
||||
FROM debian:stretch
|
||||
|
||||
RUN dpkg --add-architecture armhf
|
||||
RUN dpkg --add-architecture armel
|
||||
RUN apt-get update
|
||||
|
||||
RUN apt-get install -y curl build-essential crossbuild-essential-armhf crossbuild-essential-armel
|
||||
RUN apt-get install -y libasound2-dev libasound2-dev:armhf libasound2-dev:armel
|
||||
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
ENV PATH="/root/.cargo/bin/:${PATH}"
|
||||
RUN rustup target add arm-unknown-linux-gnueabi
|
||||
RUN rustup target add arm-unknown-linux-gnueabihf
|
||||
|
||||
RUN mkdir /.cargo && \
|
||||
echo '[target.arm-unknown-linux-gnueabihf]\nlinker = "arm-linux-gnueabihf-gcc"' > /.cargo/config && \
|
||||
echo '[target.arm-unknown-linux-gnueabi]\nlinker = "arm-linux-gnueabi-gcc"' >> /.cargo/config
|
||||
|
||||
RUN mkdir /build
|
||||
ENV CARGO_TARGET_DIR /build
|
||||
ENV CARGO_HOME /build/cache
|
||||
|
||||
ADD . /src
|
||||
WORKDIR /src
|
||||
CMD ["/src/contrib/docker-build.sh"]
|
6
contrib/docker-build.sh
Executable file
6
contrib/docker-build.sh
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
set -eux
|
||||
|
||||
cargo build --release --no-default-features --features "with-syntex alsa-backend"
|
||||
cargo build --release --target arm-unknown-linux-gnueabihf --no-default-features --features "with-syntex alsa-backend"
|
||||
cargo build --release --target arm-unknown-linux-gnueabi --no-default-features --features "with-syntex alsa-backend"
|
|
@ -73,20 +73,19 @@ use self::pipe::StdoutSink;
|
|||
|
||||
declare_backends! {
|
||||
pub const BACKENDS : &'static [
|
||||
(&'static str,
|
||||
&'static (Fn(Option<String>) -> Box<Sink> + Sync + Send + 'static))
|
||||
(&'static str, fn(Option<String>) -> Box<Sink>)
|
||||
] = &[
|
||||
#[cfg(feature = "alsa-backend")]
|
||||
("alsa", &mk_sink::<AlsaSink>),
|
||||
("alsa", mk_sink::<AlsaSink>),
|
||||
#[cfg(feature = "portaudio-backend")]
|
||||
("portaudio", &mk_sink::<PortAudioSink>),
|
||||
("portaudio", mk_sink::<PortAudioSink>),
|
||||
#[cfg(feature = "pulseaudio-backend")]
|
||||
("pulseaudio", &mk_sink::<PulseAudioSink>),
|
||||
("pipe", &mk_sink::<StdoutSink>),
|
||||
("pulseaudio", mk_sink::<PulseAudioSink>),
|
||||
("pipe", mk_sink::<StdoutSink>),
|
||||
];
|
||||
}
|
||||
|
||||
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<&'static (Fn(Option<String>) -> Box<Sink> + Send + Sync)> {
|
||||
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<String>) -> Box<Sink>> {
|
||||
if let Some(name) = name.as_ref().map(AsRef::as_ref) {
|
||||
BACKENDS.iter().find(|backend| name == backend.0).map(|backend| backend.1)
|
||||
} else {
|
||||
|
|
|
@ -64,6 +64,7 @@ pub mod player;
|
|||
pub mod session;
|
||||
pub mod util;
|
||||
pub mod version;
|
||||
pub mod mixer;
|
||||
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
|
||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -21,6 +21,8 @@ use librespot::audio_backend::{self, Sink, BACKENDS};
|
|||
use librespot::cache::Cache;
|
||||
use librespot::player::Player;
|
||||
use librespot::session::{Bitrate, Config, Session};
|
||||
use librespot::mixer::{self, Mixer};
|
||||
|
||||
use librespot::version;
|
||||
|
||||
fn usage(program: &str, opts: &getopts::Options) -> String {
|
||||
|
@ -62,9 +64,9 @@ fn list_backends() {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Setup {
|
||||
backend: &'static (Fn(Option<String>) -> Box<Sink> + Send + Sync),
|
||||
backend: fn(Option<String>) -> Box<Sink>,
|
||||
mixer: Box<Mixer + Send>,
|
||||
cache: Option<Cache>,
|
||||
config: Config,
|
||||
credentials: Credentials,
|
||||
|
@ -82,7 +84,8 @@ fn setup(args: &[String]) -> Setup {
|
|||
.optopt("u", "username", "Username to sign in with", "USERNAME")
|
||||
.optopt("p", "password", "Password", "PASSWORD")
|
||||
.optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND")
|
||||
.optopt("", "device", "Audio device to use. Use '?' to list options", "DEVICE");
|
||||
.optopt("", "device", "Audio device to use. Use '?' to list options", "DEVICE")
|
||||
.optopt("", "mixer", "Mixer to use", "MIXER");
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
|
@ -109,6 +112,10 @@ fn setup(args: &[String]) -> Setup {
|
|||
let backend = audio_backend::find(backend_name.as_ref())
|
||||
.expect("Invalid backend");
|
||||
|
||||
let mixer_name = matches.opt_str("mixer");
|
||||
let mixer = mixer::find(mixer_name.as_ref())
|
||||
.expect("Invalid mixer");
|
||||
|
||||
let bitrate = matches.opt_str("b").as_ref()
|
||||
.map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
|
||||
.unwrap_or(Bitrate::Bitrate160);
|
||||
|
@ -140,6 +147,7 @@ fn setup(args: &[String]) -> Setup {
|
|||
|
||||
Setup {
|
||||
backend: backend,
|
||||
mixer: mixer,
|
||||
cache: cache,
|
||||
config: config,
|
||||
credentials: credentials,
|
||||
|
@ -153,16 +161,18 @@ fn main() {
|
|||
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
let Setup { backend, cache, config, credentials, device } = setup(&args);
|
||||
let Setup { backend, mixer, cache, config, credentials, device }
|
||||
= setup(&args);
|
||||
|
||||
let connection = Session::connect(config, credentials, cache, handle);
|
||||
|
||||
let task = connection.and_then(move |session| {
|
||||
let player = Player::new(session.clone(), move || {
|
||||
let audio_filter = mixer.get_audio_filter();
|
||||
let player = Player::new(session.clone(), audio_filter, move || {
|
||||
(backend)(device)
|
||||
});
|
||||
|
||||
let (spirc, task) = Spirc::new(session.clone(), player);
|
||||
let (spirc, task) = Spirc::new(session.clone(), player, mixer);
|
||||
let spirc = ::std::cell::RefCell::new(spirc);
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
|
|
23
src/mixer/mod.rs
Normal file
23
src/mixer/mod.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
pub trait Mixer {
|
||||
fn start(&self);
|
||||
fn stop(&self);
|
||||
fn set_volume(&self, volume: u16);
|
||||
fn volume(&self) -> u16;
|
||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AudioFilter {
|
||||
fn modify_stream(&self, data: &mut [i16]);
|
||||
}
|
||||
|
||||
pub mod softmixer;
|
||||
use self::softmixer::SoftMixer;
|
||||
|
||||
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<Box<Mixer + Send>> {
|
||||
match name.as_ref().map(AsRef::as_ref) {
|
||||
None | Some("softvol") => Some(Box::new(SoftMixer::new())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
48
src/mixer/softmixer.rs
Normal file
48
src/mixer/softmixer.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use super::Mixer;
|
||||
use super::AudioFilter;
|
||||
|
||||
pub struct SoftMixer {
|
||||
volume: Arc<AtomicUsize>
|
||||
}
|
||||
|
||||
impl SoftMixer {
|
||||
pub fn new() -> SoftMixer {
|
||||
SoftMixer {
|
||||
volume: Arc::new(AtomicUsize::new(0xFFFF))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mixer for SoftMixer {
|
||||
fn start(&self) {
|
||||
}
|
||||
fn stop(&self) {
|
||||
}
|
||||
fn volume(&self) -> u16 {
|
||||
self.volume.load(Ordering::Relaxed) as u16
|
||||
}
|
||||
fn set_volume(&self, volume: u16) {
|
||||
self.volume.store(volume as usize, Ordering::Relaxed);
|
||||
}
|
||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
||||
Some(Box::new(SoftVolumeApplier { volume: self.volume.clone() }))
|
||||
}
|
||||
}
|
||||
|
||||
struct SoftVolumeApplier {
|
||||
volume: Arc<AtomicUsize>
|
||||
}
|
||||
|
||||
impl AudioFilter for SoftVolumeApplier {
|
||||
fn modify_stream(&self, data: &mut [i16]) {
|
||||
let volume = self.volume.load(Ordering::Relaxed) as u16;
|
||||
if volume != 0xFFFF {
|
||||
for x in data.iter_mut() {
|
||||
*x = (*x as i32 * volume as i32 / 0xFFFF) as i16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ use audio_decrypt::AudioDecrypt;
|
|||
use audio_file::AudioFile;
|
||||
use metadata::{FileFormat, Track};
|
||||
use session::{Bitrate, Session};
|
||||
use mixer::AudioFilter;
|
||||
use util::{self, SpotifyId, Subfile};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -25,21 +26,20 @@ struct PlayerInternal {
|
|||
commands: std::sync::mpsc::Receiver<PlayerCommand>,
|
||||
|
||||
state: PlayerState,
|
||||
volume: u16,
|
||||
sink: Box<Sink>,
|
||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
||||
}
|
||||
|
||||
enum PlayerCommand {
|
||||
Load(SpotifyId, bool, u32, oneshot::Sender<()>),
|
||||
Play,
|
||||
Pause,
|
||||
Volume(u16),
|
||||
Stop,
|
||||
Seek(u32),
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new<F>(session: Session, sink_builder: F) -> Player
|
||||
pub fn new<F>(session: Session, audio_filter: Option<Box<AudioFilter + Send>>, sink_builder: F) -> Player
|
||||
where F: FnOnce() -> Box<Sink> + Send + 'static {
|
||||
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
|
||||
|
||||
|
@ -49,8 +49,8 @@ impl Player {
|
|||
commands: cmd_rx,
|
||||
|
||||
state: PlayerState::Stopped,
|
||||
volume: 0xFFFF,
|
||||
sink: sink_builder(),
|
||||
audio_filter: audio_filter,
|
||||
};
|
||||
|
||||
internal.run();
|
||||
|
@ -89,10 +89,6 @@ impl Player {
|
|||
pub fn seek(&self, position_ms: u32) {
|
||||
self.command(PlayerCommand::Seek(position_ms));
|
||||
}
|
||||
|
||||
pub fn volume(&self, vol: u16) {
|
||||
self.command(PlayerCommand::Volume(vol));
|
||||
}
|
||||
}
|
||||
|
||||
type Decoder = vorbis::Decoder<Subfile<AudioDecrypt<AudioFile>>>;
|
||||
|
@ -203,11 +199,9 @@ impl PlayerInternal {
|
|||
fn handle_packet(&mut self, packet: Option<Result<vorbis::Packet, VorbisError>>) {
|
||||
match packet {
|
||||
Some(Ok(mut packet)) => {
|
||||
if self.volume < 0xFFFF {
|
||||
for x in &mut packet.data {
|
||||
*x = (*x as i32 * self.volume as i32 / 0xFFFF) as i16;
|
||||
}
|
||||
}
|
||||
if let Some(ref editor) = self.audio_filter {
|
||||
editor.modify_stream(&mut packet.data)
|
||||
};
|
||||
|
||||
self.sink.write(&packet.data).unwrap();
|
||||
}
|
||||
|
@ -313,10 +307,6 @@ impl PlayerInternal {
|
|||
PlayerState::Invalid => panic!("invalid state"),
|
||||
}
|
||||
}
|
||||
|
||||
PlayerCommand::Volume(vol) => {
|
||||
self.volume = vol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -419,11 +409,6 @@ impl ::std::fmt::Debug for PlayerCommand {
|
|||
PlayerCommand::Pause => {
|
||||
f.debug_tuple("Pause").finish()
|
||||
}
|
||||
PlayerCommand::Volume(volume) => {
|
||||
f.debug_tuple("Volume")
|
||||
.field(&volume)
|
||||
.finish()
|
||||
}
|
||||
PlayerCommand::Stop => {
|
||||
f.debug_tuple("Stop").finish()
|
||||
}
|
||||
|
|
73
src/spirc.rs
73
src/spirc.rs
|
@ -7,6 +7,7 @@ use protobuf::{self, Message};
|
|||
|
||||
use mercury::MercuryError;
|
||||
use player::Player;
|
||||
use mixer::Mixer;
|
||||
use session::Session;
|
||||
use util::{now_ms, SpotifyId, SeqGenerator};
|
||||
use version;
|
||||
|
@ -16,6 +17,7 @@ use protocol::spirc::{PlayStatus, State, MessageType, Frame, DeviceState};
|
|||
|
||||
pub struct SpircTask {
|
||||
player: Player,
|
||||
mixer: Box<Mixer + Send>,
|
||||
|
||||
sequence: SeqGenerator<u32>,
|
||||
|
||||
|
@ -43,7 +45,6 @@ fn initial_state() -> State {
|
|||
protobuf_init!(protocol::spirc::State::new(), {
|
||||
repeat: false,
|
||||
shuffle: false,
|
||||
|
||||
status: PlayStatus::kPlayStatusStop,
|
||||
position_ms: 0,
|
||||
position_measured_at: 0,
|
||||
|
@ -109,7 +110,9 @@ fn initial_device_state(name: String, volume: u16) -> DeviceState {
|
|||
}
|
||||
|
||||
impl Spirc {
|
||||
pub fn new(session: Session, player: Player) -> (Spirc, SpircTask) {
|
||||
pub fn new(session: Session, player: Player, mixer: Box<Mixer + Send>)
|
||||
-> (Spirc, SpircTask)
|
||||
{
|
||||
let ident = session.device_id().to_owned();
|
||||
let name = session.config().name.clone();
|
||||
|
||||
|
@ -130,10 +133,11 @@ impl Spirc {
|
|||
|
||||
let volume = 0xFFFF;
|
||||
let device = initial_device_state(name, volume);
|
||||
player.volume(volume);
|
||||
mixer.set_volume(volume);
|
||||
|
||||
let mut task = SpircTask {
|
||||
player: player,
|
||||
mixer: mixer,
|
||||
|
||||
sequence: SeqGenerator::new(1),
|
||||
|
||||
|
@ -269,6 +273,7 @@ impl SpircTask {
|
|||
|
||||
MessageType::kMessageTypePlay => {
|
||||
if self.state.get_status() == PlayStatus::kPlayStatusPause {
|
||||
self.mixer.start();
|
||||
self.player.play();
|
||||
self.state.set_status(PlayStatus::kPlayStatusPlay);
|
||||
self.state.set_position_measured_at(now_ms() as u64);
|
||||
|
@ -280,6 +285,7 @@ impl SpircTask {
|
|||
MessageType::kMessageTypePause => {
|
||||
if self.state.get_status() == PlayStatus::kPlayStatusPlay {
|
||||
self.player.pause();
|
||||
self.mixer.stop();
|
||||
self.state.set_status(PlayStatus::kPlayStatusPause);
|
||||
|
||||
let now = now_ms() as u64;
|
||||
|
@ -349,7 +355,7 @@ impl SpircTask {
|
|||
MessageType::kMessageTypeVolume => {
|
||||
let volume = frame.get_volume();
|
||||
self.device.set_volume(volume);
|
||||
self.player.volume(volume as u16);
|
||||
self.mixer.set_volume(frame.get_volume() as u16);
|
||||
self.notify(None);
|
||||
}
|
||||
|
||||
|
@ -360,6 +366,7 @@ impl SpircTask {
|
|||
self.device.set_is_active(false);
|
||||
self.state.set_status(PlayStatus::kPlayStatusStop);
|
||||
self.player.stop();
|
||||
self.mixer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,7 +405,6 @@ impl SpircTask {
|
|||
let gid = self.state.get_track()[index as usize].get_gid();
|
||||
SpotifyId::from_raw(gid)
|
||||
};
|
||||
|
||||
let position = self.state.get_position_ms();
|
||||
|
||||
let end_of_track = self.player.load(track, play, position);
|
||||
|
@ -423,52 +429,49 @@ impl SpircTask {
|
|||
}
|
||||
cs.send();
|
||||
}
|
||||
|
||||
fn spirc_state(&self) -> protocol::spirc::State {
|
||||
self.state.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct CommandSender<'a> {
|
||||
spirc: &'a mut SpircTask,
|
||||
cmd: MessageType,
|
||||
recipient: Option<String>,
|
||||
frame: protocol::spirc::Frame,
|
||||
}
|
||||
|
||||
impl<'a> CommandSender<'a> {
|
||||
fn new(spirc: &'a mut SpircTask, cmd: MessageType) -> CommandSender {
|
||||
CommandSender {
|
||||
spirc: spirc,
|
||||
cmd: cmd,
|
||||
recipient: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn recipient(mut self, r: &str) -> CommandSender<'a> {
|
||||
self.recipient = Some(r.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
fn send(self) {
|
||||
let mut frame = protobuf_init!(Frame::new(), {
|
||||
let frame = protobuf_init!(protocol::spirc::Frame::new(), {
|
||||
version: 1,
|
||||
ident: self.spirc.ident.clone(),
|
||||
protocol_version: "2.0.0",
|
||||
seq_nr: self.spirc.sequence.get(),
|
||||
typ: self.cmd,
|
||||
device_state: self.spirc.device.clone(),
|
||||
ident: spirc.ident.clone(),
|
||||
seq_nr: spirc.sequence.get(),
|
||||
typ: cmd,
|
||||
|
||||
device_state: spirc.device.clone(),
|
||||
state_update_id: now_ms(),
|
||||
});
|
||||
|
||||
if let Some(recipient) = self.recipient {
|
||||
frame.mut_recipient().push(recipient.to_owned());
|
||||
CommandSender {
|
||||
spirc: spirc,
|
||||
frame: frame,
|
||||
}
|
||||
}
|
||||
|
||||
if self.spirc.device.get_is_active() {
|
||||
frame.set_state(self.spirc.spirc_state());
|
||||
fn recipient(mut self, recipient: &'a str) -> CommandSender {
|
||||
self.frame.mut_recipient().push(recipient.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
let ready = self.spirc.sender.start_send(frame).unwrap().is_ready();
|
||||
assert!(ready);
|
||||
#[allow(dead_code)]
|
||||
fn state(mut self, state: protocol::spirc::State) -> CommandSender<'a> {
|
||||
self.frame.set_state(state);
|
||||
self
|
||||
}
|
||||
|
||||
fn send(mut self) {
|
||||
if !self.frame.has_state() && self.spirc.device.get_is_active() {
|
||||
self.frame.set_state(self.spirc.state.clone());
|
||||
}
|
||||
|
||||
let send = self.spirc.sender.start_send(self.frame).unwrap();
|
||||
assert!(send.is_ready());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue