dlna_dmr/
lib.rs

1//! # `dlna-dmr` library crate
2//!
3//! If you are reading this, you are reading the documentation for the `dlna-dmr` library crate. For the cli, kindly refer to the README file.
4//!
5//! ## Overview
6//!
7//! This crate provides a framework for building a Digital Media Renderer (DMR). It only provides the functionality of accepting commands from a Digital Media Controller (DMC), and how to handle them will be left to you to implement.
8//!
9//! ## Usage
10//!
11//! To build your DMR, you'll first need to implement the [`HTTPServer`] trait, which describes how to handle various commands:
12//!
13//! ```rust
14//! use dlna_dmr::HTTPServer;
15//!
16//! struct MyDMR {}
17//!
18//! impl HTTPServer for MyDMR {
19//!   // Refer to the documentation of `HTTPServer` on how to implement.
20//! }
21//! ```
22//!
23//! Then, you simply implement the [`DMR`] trait:
24//!
25//! ```rust
26//! use dlna_dmr::{DMR, HTTPServer};
27//! #
28//! # struct MyDMR {}
29//! #
30//! # impl HTTPServer for MyDMR {
31//! # }
32//! impl DMR for MyDMR {}
33//! ```
34//!
35//! To start your DMR, call the method [`DMR::run`] with an option:
36//!
37//! ```rust
38//! use dlna_dmr::{DMR, DMROptions, HTTPServer};
39//! use std::sync::Arc;
40//! #
41//! # struct MyDMR {}
42//! #
43//! # impl HTTPServer for MyDMR {
44//! # }
45//! # impl DMR for MyDMR {}
46//!
47//! # async fn run() { // This function won't be run intentionally
48//!     // Instantiate `MyDMR`
49//!     let dmr = MyDMR {};
50//!     let dmr = Box::leak(Box::new(dmr));
51//!     // Use default config (Refer to documentation of `DMROptions` on configuration)
52//!     let options = DMROptions::default();
53//!     // Running the DMR until Ctrl-C is pressed.
54//!     dmr.run(Arc::new(options)).await.unwrap();
55//! # }
56//! ```
57
58#![deny(missing_docs)]
59#![warn(clippy::all, clippy::nursery, clippy::pedantic, clippy::cargo)]
60#![allow(clippy::multiple_crate_versions, reason = "Dependencies' requirements")]
61
62mod defaults;
63mod http;
64mod ssdp;
65pub mod xml;
66
67pub use axum::response::Response;
68pub use http::HTTPServer;
69use log::{error, info};
70use serde::{Deserialize, Serialize};
71use ssdp::SSDPServer;
72use std::{
73    net::{Ipv4Addr, SocketAddrV4},
74    sync::Arc,
75    io::Result as IoResult,
76};
77
78/// Options for a DMR instance.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct DMROptions {
81    /// Local IP.
82    #[serde(default = "defaults::ip")]
83    pub ip: Ipv4Addr,
84    /// The SSDP server port.
85    #[serde(default = "defaults::ssdp_port")]
86    pub ssdp_port: u16,
87    /// The HTTP server port.
88    #[serde(default = "defaults::http_port")]
89    pub http_port: u16,
90    /// The UUID of the DMR instance.
91    #[serde(default = "defaults::uuid")]
92    pub uuid: String,
93    /// Friendly name of the DMR instance.
94    #[serde(default = "defaults::friendly_name")]
95    pub friendly_name: String,
96    /// Model name of the DMR instance.
97    #[serde(default = "defaults::model_name")]
98    pub model_name: String,
99    /// Model description of the DMR instance.
100    #[serde(default = "defaults::model_description")]
101    pub model_description: String,
102    /// Model URL of the DMR instance.
103    #[serde(default = "defaults::model_url")]
104    pub model_url: String,
105    /// Manufacturer of the DMR instance.
106    #[serde(default = "defaults::manufacturer")]
107    pub manufacturer: String,
108    /// Manufacturer URL of the DMR instance.
109    #[serde(default = "defaults::manufacturer_url")]
110    pub manufacturer_url: String,
111    /// Serial number of the DMR instance.
112    #[serde(default = "defaults::serial_number")]
113    pub serial_number: String,
114}
115
116impl Default for DMROptions {
117    fn default() -> Self {
118        Self {
119            ip: defaults::ip(),
120            ssdp_port: defaults::ssdp_port(),
121            http_port: defaults::http_port(),
122            uuid: defaults::uuid(),
123            friendly_name: defaults::friendly_name(),
124            model_name: defaults::model_name(),
125            model_description: defaults::model_description(),
126            model_url: defaults::model_url(),
127            manufacturer: defaults::manufacturer(),
128            manufacturer_url: defaults::manufacturer_url(),
129            serial_number: defaults::serial_number(),
130        }
131    }
132}
133
134/// A trait for DMR instances.
135pub trait DMR: HTTPServer {
136    /// Create and run the DMR instance, stopping when Ctrl-C is pressed.
137    fn run(&'static self, options: Arc<DMROptions>) -> impl Future<Output = IoResult<()>> + Send
138    where
139        Self: Sync,
140    {async {
141        let address = SocketAddrV4::new(options.ip, options.ssdp_port);
142        let ssdp = SSDPServer::new(
143            address,
144            options.uuid.clone(),
145            options.http_port,
146        )
147        .await?;
148
149        tokio::select! {
150            _ = ssdp.keep_alive() => {}
151            _ = ssdp.run() => {}
152            r = self.run_http(options) => {
153                if let Err(e) = r {
154                    error!("IO Error while running HTTP server: {e}");
155                }
156            }
157            r = tokio::signal::ctrl_c() => {
158                if let Err(e) = r {
159                    error!("IO Error while waiting for Ctrl-C: {e}");
160                }
161            }
162        }
163
164        ssdp.stop().await;
165
166        info!("DMR stopped");
167        Ok(())
168    } }
169}