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}