mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2024-12-27 07:35:56 -05:00
allow setting rtsp transport per-stream
This commit is contained in:
parent
981cee0706
commit
1c9a55653d
@ -10,6 +10,7 @@ Each release is tagged in Git and on the Docker repository
|
||||
|
||||
* bugfix: editing a camera from `nvr config` would erroneously clear the
|
||||
sample file directory associated with its streams.
|
||||
* RTSP transport (TCP or UDP) can be set per-stream from `nvr config`.
|
||||
|
||||
## `v0.7.0` (2021-10-27)
|
||||
|
||||
|
@ -209,6 +209,10 @@ pub struct StreamConfig {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<Url>,
|
||||
|
||||
/// The RTSP transport (`tcp` or `udp`) to use.
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub rtsp_transport: String,
|
||||
|
||||
/// The number of bytes of video to retain, excluding the
|
||||
/// currently-recording file.
|
||||
///
|
||||
|
@ -13,6 +13,7 @@ use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Camera {
|
||||
short_name: String,
|
||||
description: String,
|
||||
@ -22,11 +23,12 @@ struct Camera {
|
||||
streams: [Stream; db::NUM_STREAM_TYPES],
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Debug, Default)]
|
||||
struct Stream {
|
||||
url: String,
|
||||
record: bool,
|
||||
flush_if_sec: String,
|
||||
rtsp_transport: &'static str,
|
||||
sample_file_dir_id: Option<i32>,
|
||||
}
|
||||
|
||||
@ -82,6 +84,11 @@ fn get_camera(siv: &mut Cursive) -> Camera {
|
||||
.find_name::<views::Checkbox>(&format!("{}_record", t.as_str()))
|
||||
.unwrap()
|
||||
.is_checked();
|
||||
let rtsp_transport = *siv
|
||||
.find_name::<views::SelectView<&'static str>>(&format!("{}_rtsp_transport", t.as_str()))
|
||||
.unwrap()
|
||||
.selection()
|
||||
.unwrap();
|
||||
let flush_if_sec = siv
|
||||
.find_name::<views::EditView>(&format!("{}_flush_if_sec", t.as_str()))
|
||||
.unwrap()
|
||||
@ -97,9 +104,11 @@ fn get_camera(siv: &mut Cursive) -> Camera {
|
||||
url,
|
||||
record,
|
||||
flush_if_sec,
|
||||
rtsp_transport,
|
||||
sample_file_dir_id,
|
||||
};
|
||||
}
|
||||
log::trace!("camera is: {:#?}", &camera);
|
||||
camera
|
||||
}
|
||||
|
||||
@ -155,6 +164,7 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
|
||||
})
|
||||
.to_owned();
|
||||
stream_change.config.url = parse_url(&stream.url, &["rtsp"])?;
|
||||
stream_change.config.rtsp_transport = stream.rtsp_transport.to_owned();
|
||||
stream_change.sample_file_dir_id = stream.sample_file_dir_id;
|
||||
stream_change.config.flush_if_sec = if stream.flush_if_sec.is_empty() {
|
||||
0
|
||||
@ -175,9 +185,13 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
|
||||
})();
|
||||
if let Err(e) = result {
|
||||
siv.add_layer(
|
||||
views::Dialog::text(format!("Unable to add camera: {}", e))
|
||||
.title("Error")
|
||||
.dismiss_button("Abort"),
|
||||
views::Dialog::text(format!(
|
||||
"Unable to {} camera: {}",
|
||||
if id.is_some() { "edit" } else { "add" },
|
||||
e
|
||||
))
|
||||
.title("Error")
|
||||
.dismiss_button("Abort"),
|
||||
);
|
||||
} else {
|
||||
siv.pop_layer(); // get rid of the add/edit camera dialog.
|
||||
@ -457,6 +471,13 @@ fn edit_camera_dialog(db: &Arc<db::Database>, siv: &mut Cursive, item: &Option<i
|
||||
"record",
|
||||
views::Checkbox::new().with_name(format!("{}_record", type_.as_str())),
|
||||
)
|
||||
.child(
|
||||
"rtsp_transport",
|
||||
views::SelectView::<&str>::new()
|
||||
.with_all([("(default)", ""), ("tcp", "tcp"), ("udp", "udp")])
|
||||
.popup()
|
||||
.with_name(format!("{}_rtsp_transport", type_.as_str())),
|
||||
)
|
||||
.child(
|
||||
"flush_if_sec",
|
||||
views::EditView::new().with_name(format!("{}_flush_if_sec", type_.as_str())),
|
||||
@ -529,11 +550,22 @@ fn edit_camera_dialog(db: &Arc<db::Database>, siv: &mut Cursive, item: &Option<i
|
||||
v.set_checked(s.config.mode == db::json::STREAM_MODE_RECORD)
|
||||
},
|
||||
);
|
||||
dialog.call_on_name(
|
||||
&format!("{}_rtsp_transport", t.as_str()),
|
||||
|v: &mut views::SelectView<&'static str>| {
|
||||
v.set_selection(match s.config.rtsp_transport.as_str() {
|
||||
"tcp" => 1,
|
||||
"udp" => 2,
|
||||
_ => 0,
|
||||
})
|
||||
},
|
||||
);
|
||||
dialog.call_on_name(
|
||||
&format!("{}_flush_if_sec", t.as_str()),
|
||||
|v: &mut views::EditView| v.set_content(s.config.flush_if_sec.to_string()),
|
||||
);
|
||||
}
|
||||
log::debug!("setting {} dir to {}", t.as_str(), selected_dir);
|
||||
dialog.call_on_name(
|
||||
&format!("{}_sample_file_dir", t.as_str()),
|
||||
|v: &mut views::SelectView<Option<i32>>| v.set_selection(selected_dir),
|
||||
|
@ -76,6 +76,8 @@ pub struct Args {
|
||||
#[structopt(long, default_value = "retina", parse(try_from_str))]
|
||||
rtsp_library: crate::stream::RtspLibrary,
|
||||
|
||||
/// The RTSP transport (`tcp` or `udp`) to use when none is specified in the
|
||||
/// per-stream configuration.
|
||||
#[structopt(long, default_value)]
|
||||
rtsp_transport: retina::client::Transport,
|
||||
}
|
||||
@ -265,7 +267,7 @@ async fn inner(args: Args, shutdown_rx: base::shutdown::Receiver) -> Result<i32,
|
||||
let env = streamer::Environment {
|
||||
db: &db,
|
||||
opener: args.rtsp_library.opener(),
|
||||
transport: args.rtsp_transport,
|
||||
default_transport: args.rtsp_transport,
|
||||
shutdown_rx: &shutdown_rx,
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,7 @@ use db::{dir, recording, writer, Camera, Database, Stream};
|
||||
use failure::{bail, format_err, Error};
|
||||
use log::{debug, info, trace, warn};
|
||||
use std::result::Result;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
@ -19,7 +20,7 @@ where
|
||||
C: Clocks + Clone,
|
||||
{
|
||||
pub opener: &'a dyn stream::Opener,
|
||||
pub transport: retina::client::Transport,
|
||||
pub default_transport: retina::client::Transport,
|
||||
pub db: &'tmp Arc<Database<C>>,
|
||||
pub shutdown_rx: &'tmp base::shutdown::Receiver,
|
||||
}
|
||||
@ -71,6 +72,22 @@ where
|
||||
if !url.username().is_empty() || url.password().is_some() {
|
||||
bail!("RTSP URL shouldn't include credentials");
|
||||
}
|
||||
let stream_transport = if s.config.rtsp_transport.is_empty() {
|
||||
None
|
||||
} else {
|
||||
match retina::client::Transport::from_str(&s.config.rtsp_transport) {
|
||||
Ok(t) => Some(t),
|
||||
Err(_) => {
|
||||
log::warn!(
|
||||
"Unable to parse configured transport {:?} for {}/{}; ignoring.",
|
||||
&s.config.rtsp_transport,
|
||||
&c.short_name,
|
||||
s.type_
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(Streamer {
|
||||
shutdown_rx: env.shutdown_rx.clone(),
|
||||
rotate_offset_sec,
|
||||
@ -79,7 +96,7 @@ where
|
||||
dir,
|
||||
syncer_channel,
|
||||
opener: env.opener,
|
||||
transport: env.transport,
|
||||
transport: stream_transport.unwrap_or(env.default_transport),
|
||||
stream_id,
|
||||
session_group,
|
||||
short_name: format!("{}-{}", c.short_name, s.type_.as_str()),
|
||||
@ -417,7 +434,7 @@ mod tests {
|
||||
opener: &opener,
|
||||
db: &db.db,
|
||||
shutdown_rx: &shutdown_rx,
|
||||
transport: retina::client::Transport::Tcp,
|
||||
default_transport: retina::client::Transport::Tcp,
|
||||
};
|
||||
let mut stream;
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user