Skip to content

Commit d2d733a

Browse files
committed
Run librespot on a separate thread. Expose futures to Python.
1 parent 5830de4 commit d2d733a

File tree

3 files changed

+58
-19
lines changed

3 files changed

+58
-19
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ crate-type = ["cdylib"]
99

1010
[dependencies]
1111
tokio-core = "0.1.2"
12+
futures = "0.1.0"
1213

1314
[dependencies.librespot]
1415
git = "https://github.com/plietar/librespot"

examples/play.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
[_, username, password, trackid] = sys.argv
99

1010
print("Connecting ...")
11-
session = Session(username, password)
11+
session = Session.connect(username, password).wait()
1212
player = session.player()
1313

1414
print("Playing ...")
1515
track = SpotifyId(trackid)
16-
player.play(track)
16+
player.load(track).wait()
1717

1818
print("Done")

src/lib.rs

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,71 @@
11
#[macro_use]
22
extern crate cpython;
3-
extern crate tokio_core;
3+
extern crate futures;
44
extern crate librespot;
5+
extern crate tokio_core;
56

6-
use std::cell::RefCell;
7-
use cpython::{PyResult, PyObject};
7+
use cpython::{PyResult, PyObject, Python, PythonObject};
8+
use futures::Future;
9+
use std::thread;
810
use tokio_core::reactor::Core;
11+
use std::cell::RefCell;
912

10-
thread_local! {
11-
pub static CORE: RefCell<Core> = RefCell::new(Core::new().unwrap());
13+
// Workaround rust-lang/rust#28796
14+
trait Callback : Send {
15+
fn call(self: Box<Self>, py: Python) -> PyResult<PyObject>;
16+
}
17+
impl <F: Send + for<'a> FnOnce(Python<'a>) -> PyResult<PyObject>> Callback for F {
18+
fn call(self: Box<Self>, py: Python) -> PyResult<PyObject> {
19+
(*self)(py)
20+
}
21+
}
22+
23+
py_class!(class PyFuture |py| {
24+
data callback : RefCell<Option<Box<Callback>>>;
25+
26+
def wait(&self) -> PyResult<PyObject> {
27+
let callback = self.callback(py).borrow_mut().take().expect("Future already completed");
28+
callback.call(py)
29+
}
30+
});
31+
32+
impl PyFuture {
33+
pub fn new<F, T, U>(py: Python, future: F, then: T) -> PyResult<PyFuture>
34+
where F: Future + Send + 'static,
35+
T: FnOnce(Python, Result<F::Item, F::Error>) -> PyResult<U> + Send + 'static,
36+
U: PythonObject
37+
{
38+
PyFuture::create_instance(py, RefCell::new(Some(Box::new(move |py: Python| {
39+
let result = future.wait();
40+
then(py, result).map(PythonObject::into_object)
41+
}))))
42+
}
1243
}
1344

1445
py_class!(class Session |py| {
1546
data session : librespot::session::Session;
1647

17-
def __new__(_cls, username: String, password: String) -> PyResult<Session> {
48+
@classmethod def connect(_cls, username: String, password: String) -> PyResult<PyFuture> {
1849
use librespot::session::Config;
1950
use librespot::authentication::Credentials;
2051

21-
CORE.with(|core| {
22-
let mut core = core.borrow_mut();
52+
let config = Config::default();
53+
let credentials = Credentials::with_password(username, password);
54+
55+
let (tx, rx) = futures::sync::oneshot::channel();
56+
thread::spawn(move || {
57+
let mut core = Core::new().unwrap();
2358
let handle = core.handle();
2459

25-
let config = Config::default();
26-
let credentials = Credentials::with_password(username, password);
2760
let session = core.run(librespot::session::Session::connect(config, credentials, None, handle)).unwrap();
2861

62+
let _ = tx.send(session);
63+
64+
core.run(futures::future::empty::<(), ()>()).unwrap();
65+
});
66+
67+
PyFuture::new(py, rx, |py, result| {
68+
let session = result.unwrap();
2969
Session::create_instance(py, session)
3070
})
3171
}
@@ -42,14 +82,12 @@ py_class!(class Session |py| {
4282
py_class!(class Player |py| {
4383
data player : librespot::player::Player;
4484

45-
def play(&self, track: SpotifyId) -> PyResult<PyObject> {
46-
CORE.with(|core| {
47-
let mut core = core.borrow_mut();
48-
let player = self.player(py);
49-
let track = *track.id(py);
50-
51-
core.run(player.load(track, true, 0)).unwrap();
85+
def load(&self, track: SpotifyId, play: bool = true, position_ms: u32 = 0) -> PyResult<PyFuture> {
86+
let player = self.player(py);
87+
let track = *track.id(py);
5288

89+
let end_of_track = player.load(track, play, position_ms);
90+
PyFuture::new(py, end_of_track, |py, _result| {
5391
Ok(py.None())
5492
})
5593
}

0 commit comments

Comments
 (0)