mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2024-12-27 23:55: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
|
* bugfix: editing a camera from `nvr config` would erroneously clear the
|
||||||
sample file directory associated with its streams.
|
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)
|
## `v0.7.0` (2021-10-27)
|
||||||
|
|
||||||
|
@ -209,6 +209,10 @@ pub struct StreamConfig {
|
|||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub url: Option<Url>,
|
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
|
/// The number of bytes of video to retain, excluding the
|
||||||
/// currently-recording file.
|
/// currently-recording file.
|
||||||
///
|
///
|
||||||
|
@ -13,6 +13,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Camera {
|
struct Camera {
|
||||||
short_name: String,
|
short_name: String,
|
||||||
description: String,
|
description: String,
|
||||||
@ -22,11 +23,12 @@ struct Camera {
|
|||||||
streams: [Stream; db::NUM_STREAM_TYPES],
|
streams: [Stream; db::NUM_STREAM_TYPES],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Stream {
|
struct Stream {
|
||||||
url: String,
|
url: String,
|
||||||
record: bool,
|
record: bool,
|
||||||
flush_if_sec: String,
|
flush_if_sec: String,
|
||||||
|
rtsp_transport: &'static str,
|
||||||
sample_file_dir_id: Option<i32>,
|
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()))
|
.find_name::<views::Checkbox>(&format!("{}_record", t.as_str()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_checked();
|
.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
|
let flush_if_sec = siv
|
||||||
.find_name::<views::EditView>(&format!("{}_flush_if_sec", t.as_str()))
|
.find_name::<views::EditView>(&format!("{}_flush_if_sec", t.as_str()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -97,9 +104,11 @@ fn get_camera(siv: &mut Cursive) -> Camera {
|
|||||||
url,
|
url,
|
||||||
record,
|
record,
|
||||||
flush_if_sec,
|
flush_if_sec,
|
||||||
|
rtsp_transport,
|
||||||
sample_file_dir_id,
|
sample_file_dir_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
log::trace!("camera is: {:#?}", &camera);
|
||||||
camera
|
camera
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +164,7 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
|
|||||||
})
|
})
|
||||||
.to_owned();
|
.to_owned();
|
||||||
stream_change.config.url = parse_url(&stream.url, &["rtsp"])?;
|
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.sample_file_dir_id = stream.sample_file_dir_id;
|
||||||
stream_change.config.flush_if_sec = if stream.flush_if_sec.is_empty() {
|
stream_change.config.flush_if_sec = if stream.flush_if_sec.is_empty() {
|
||||||
0
|
0
|
||||||
@ -175,9 +185,13 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
|
|||||||
})();
|
})();
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
siv.add_layer(
|
siv.add_layer(
|
||||||
views::Dialog::text(format!("Unable to add camera: {}", e))
|
views::Dialog::text(format!(
|
||||||
.title("Error")
|
"Unable to {} camera: {}",
|
||||||
.dismiss_button("Abort"),
|
if id.is_some() { "edit" } else { "add" },
|
||||||
|
e
|
||||||
|
))
|
||||||
|
.title("Error")
|
||||||
|
.dismiss_button("Abort"),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
siv.pop_layer(); // get rid of the add/edit camera dialog.
|
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",
|
"record",
|
||||||
views::Checkbox::new().with_name(format!("{}_record", type_.as_str())),
|
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(
|
.child(
|
||||||
"flush_if_sec",
|
"flush_if_sec",
|
||||||
views::EditView::new().with_name(format!("{}_flush_if_sec", type_.as_str())),
|
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)
|
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(
|
dialog.call_on_name(
|
||||||
&format!("{}_flush_if_sec", t.as_str()),
|
&format!("{}_flush_if_sec", t.as_str()),
|
||||||
|v: &mut views::EditView| v.set_content(s.config.flush_if_sec.to_string()),
|
|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(
|
dialog.call_on_name(
|
||||||
&format!("{}_sample_file_dir", t.as_str()),
|
&format!("{}_sample_file_dir", t.as_str()),
|
||||||
|v: &mut views::SelectView<Option<i32>>| v.set_selection(selected_dir),
|
|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))]
|
#[structopt(long, default_value = "retina", parse(try_from_str))]
|
||||||
rtsp_library: crate::stream::RtspLibrary,
|
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)]
|
#[structopt(long, default_value)]
|
||||||
rtsp_transport: retina::client::Transport,
|
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 {
|
let env = streamer::Environment {
|
||||||
db: &db,
|
db: &db,
|
||||||
opener: args.rtsp_library.opener(),
|
opener: args.rtsp_library.opener(),
|
||||||
transport: args.rtsp_transport,
|
default_transport: args.rtsp_transport,
|
||||||
shutdown_rx: &shutdown_rx,
|
shutdown_rx: &shutdown_rx,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ use db::{dir, recording, writer, Camera, Database, Stream};
|
|||||||
use failure::{bail, format_err, Error};
|
use failure::{bail, format_err, Error};
|
||||||
use log::{debug, info, trace, warn};
|
use log::{debug, info, trace, warn};
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ where
|
|||||||
C: Clocks + Clone,
|
C: Clocks + Clone,
|
||||||
{
|
{
|
||||||
pub opener: &'a dyn stream::Opener,
|
pub opener: &'a dyn stream::Opener,
|
||||||
pub transport: retina::client::Transport,
|
pub default_transport: retina::client::Transport,
|
||||||
pub db: &'tmp Arc<Database<C>>,
|
pub db: &'tmp Arc<Database<C>>,
|
||||||
pub shutdown_rx: &'tmp base::shutdown::Receiver,
|
pub shutdown_rx: &'tmp base::shutdown::Receiver,
|
||||||
}
|
}
|
||||||
@ -71,6 +72,22 @@ where
|
|||||||
if !url.username().is_empty() || url.password().is_some() {
|
if !url.username().is_empty() || url.password().is_some() {
|
||||||
bail!("RTSP URL shouldn't include credentials");
|
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 {
|
Ok(Streamer {
|
||||||
shutdown_rx: env.shutdown_rx.clone(),
|
shutdown_rx: env.shutdown_rx.clone(),
|
||||||
rotate_offset_sec,
|
rotate_offset_sec,
|
||||||
@ -79,7 +96,7 @@ where
|
|||||||
dir,
|
dir,
|
||||||
syncer_channel,
|
syncer_channel,
|
||||||
opener: env.opener,
|
opener: env.opener,
|
||||||
transport: env.transport,
|
transport: stream_transport.unwrap_or(env.default_transport),
|
||||||
stream_id,
|
stream_id,
|
||||||
session_group,
|
session_group,
|
||||||
short_name: format!("{}-{}", c.short_name, s.type_.as_str()),
|
short_name: format!("{}-{}", c.short_name, s.type_.as_str()),
|
||||||
@ -417,7 +434,7 @@ mod tests {
|
|||||||
opener: &opener,
|
opener: &opener,
|
||||||
db: &db.db,
|
db: &db.db,
|
||||||
shutdown_rx: &shutdown_rx,
|
shutdown_rx: &shutdown_rx,
|
||||||
transport: retina::client::Transport::Tcp,
|
default_transport: retina::client::Transport::Tcp,
|
||||||
};
|
};
|
||||||
let mut stream;
|
let mut stream;
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user