1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use crate::{
    speaker::{Speaker, SONOS_URN},
    Result,
};
use futures_util::stream::{FuturesUnordered, Stream, TryStreamExt};
use rupnp::Device;
use std::time::Duration;

// 1,408ms +/- 169ms for two devices in network
/*pub(crate) async fn discover_simple(
    timeout: Duration,
) -> Result<impl Stream<Item = Result<Speaker>>> {
    let stream = rupnp::discover(&SONOS_URN.into(), timeout)
        .await?
        .map_ok(Speaker::from_device)
        .map_ok(|device| device.expect("searched for sonos urn but got something else"));

    Ok(stream)
}*/

// 292ms +/- 191ms for two devices in network
/// Discover sonos players on the network.
///
/// # Example Usage
///
/// ```rust,no_run
/// # use futures::prelude::*;
/// # use std::time::Duration;
/// # async fn f() -> Result<(), sonor::Error> {
/// let mut devices = sonor::discover(Duration::from_secs(2)).await?;
///
/// while let Some(device) = devices.try_next().await? {
///     let name = device.name().await?;
///     println!("- {}", name);
/// }
/// # Ok(())
/// # };
pub async fn discover(timeout: Duration) -> Result<impl Stream<Item = Result<Speaker>>> {
    // this method searches for devices, but when it finds the first one it
    // uses its `.zone_group_state` to find the other devices in the network.

    let devices = rupnp::discover(&SONOS_URN.into(), timeout).await?;
    futures_util::pin_mut!(devices);

    let mut devices_iter = None;

    if let Some(device) = devices.try_next().await? {
        let iter = Speaker::from_device(device)
            .expect("searched for sonos urn but got something else")
            ._zone_group_state()
            .await?
            .into_iter()
            .flat_map(|(_, speakers)| speakers)
            .map(|speaker_info| {
                let url = speaker_info.location().parse();
                async {
                    let device = Device::from_url(url?).await?;
                    let speaker = Speaker::from_device(device);
                    Ok(speaker.expect("sonos action 'GetZoneGroupState' return non-sonos devices"))
                }
            });
        devices_iter = Some(iter);
    };

    Ok(devices_iter
        .into_iter()
        .flatten()
        .collect::<FuturesUnordered<_>>())
}

/// Search for a sonos speaker by its name.
///
/// # Example Usage
///
/// ```rust,no_run
/// # use futures::prelude::*;
/// # use std::time::Duration;
/// # async fn f() -> Result<(), sonor::Error> {
/// let speaker = sonor::find("your room name", Duration::from_secs(1)).await?
///     .expect("player exists");
/// assert_eq!(speaker.name().await?, "yoor room name");
/// # Ok(())
/// # };
pub async fn find(roomname: &str, timeout: Duration) -> Result<Option<Speaker>> {
    let mut devices = discover(timeout).await?;

    while let Some(device) = devices.try_next().await? {
        if device.name().await?.eq_ignore_ascii_case(roomname) {
            return Ok(Some(device));
        }
    }

    Ok(None)
}