├── .gitignore
├── janus-web-app
├── igalia-bg.png
├── index.html
└── webrtc.js
├── .gitmodules
├── src
├── macros.rs
├── main.rs
├── utils.rs
├── app.rs
├── settings.rs
├── pipeline.rs
└── janus.rs
├── Cargo.toml
├── LICENSE
├── README.md
└── Cargo.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/janus-web-app/igalia-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Igalia/gst-wpe-webrtc-demo/HEAD/janus-web-app/igalia-bg.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "wpe-graphics-overlays"]
2 | path = wpe-graphics-overlays
3 | url = https://github.com/Igalia/wpe-graphics-overlays.git
4 | branch = igalia
5 |
--------------------------------------------------------------------------------
/src/macros.rs:
--------------------------------------------------------------------------------
1 | // Macro for upgrading a weak reference or returning the given value
2 | //
3 | // This works for glib/gtk objects as well as anything else providing an upgrade method
4 | macro_rules! upgrade_weak {
5 | ($x:ident, $r:expr) => {{
6 | match $x.upgrade() {
7 | Some(o) => o,
8 | None => return $r,
9 | }
10 | }};
11 | ($x:ident) => {
12 | upgrade_weak!($x, ())
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | #![recursion_limit = "256"]
2 |
3 | #[macro_use]
4 | mod macros;
5 | mod app;
6 | mod janus;
7 | mod pipeline;
8 | mod settings;
9 | mod utils;
10 |
11 | use crate::app::App;
12 | #[macro_use]
13 | extern crate log;
14 |
15 | pub const APPLICATION_NAME: &str = "com.igalia.gstwpe.broadcast.demo";
16 |
17 | async fn async_main() -> Result<(), anyhow::Error> {
18 | gst::init()?;
19 | let app = App::new()?;
20 | app.run().await
21 | }
22 |
23 | fn main() -> Result<(), anyhow::Error> {
24 | env_logger::init();
25 | let main_context = glib::MainContext::default();
26 | main_context.block_on(async_main())
27 | }
28 |
--------------------------------------------------------------------------------
/janus-web-app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/utils.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 |
3 | use crate::settings::Settings;
4 | use crate::APPLICATION_NAME;
5 |
6 | // Get the default path for the settings file
7 | pub fn get_settings_file_path() -> PathBuf {
8 | let mut path = glib::get_user_config_dir().unwrap_or_else(|| PathBuf::from("."));
9 | path.push(APPLICATION_NAME);
10 | path.push("settings.toml");
11 | path
12 | }
13 |
14 | // Load the current settings
15 | pub fn load_settings() -> Settings {
16 | let s = get_settings_file_path();
17 | if s.exists() && s.is_file() {
18 | match serde_any::from_file::(&s) {
19 | Ok(s) => s,
20 | Err(e) => {
21 | panic!("Error while opening '{}': {}", s.display(), e);
22 | }
23 | }
24 | } else {
25 | Settings::default()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "gst-wpe-webrtc-demo"
3 | version = "0.1.0"
4 | authors = ["Philippe Normand "]
5 | edition = "2018"
6 | license = "MIT"
7 |
8 | [dependencies]
9 | anyhow = "1.0.31"
10 | async-tungstenite = { version = "0.7", features = ["gio-runtime"] }
11 | base64 = "0.11"
12 | futures = "0.3"
13 | gio = "0.8"
14 | glib = "0.9"
15 | gst = { package = "gstreamer", version = "0.15", features = ["v1_10"] }
16 | gst-sdp = { package = "gstreamer-sdp", version = "0.15", features = ["v1_14"] }
17 | gst-webrtc = { package = "gstreamer-webrtc", version = "0.15" }
18 | http = "0.2"
19 | num = "0.2"
20 | rand = "0.7"
21 | serde = "1"
22 | serde_any = "0.5"
23 | serde_derive = "1"
24 | serde_json = "1.0.53"
25 | strfmt = "0.1.6"
26 | url = "2"
27 | structopt = { version = "0.3", default-features = false }
28 | env_logger = "0.7.1"
29 | log = "0.4.8"
30 |
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Sebastian Dröge, Guillaume Gomez, Jordan Petridis
4 | Copyright (c) 2019 Sebastian Dröge
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
24 |
--------------------------------------------------------------------------------
/src/app.rs:
--------------------------------------------------------------------------------
1 | use crate::janus;
2 | use crate::pipeline::Pipeline;
3 |
4 | use anyhow::anyhow;
5 | use gst::prelude::*;
6 | use std::ops;
7 | use std::rc::Rc;
8 |
9 | // Our refcounted application struct for containing all the state we have to carry around.
10 | //
11 | // This represents our main application window.
12 | #[derive(Clone)]
13 | pub struct App(Rc);
14 |
15 | // Deref into the contained struct to make usage a bit more ergonomic
16 | impl ops::Deref for App {
17 | type Target = AppInner;
18 |
19 | fn deref(&self) -> &AppInner {
20 | &*self.0
21 | }
22 | }
23 |
24 | pub struct AppInner {
25 | pipeline: Pipeline,
26 | }
27 |
28 | impl App {
29 | pub fn new() -> Result {
30 | let pipeline =
31 | Pipeline::new().map_err(|err| anyhow!("Error creating pipeline: {:?}", err))?;
32 |
33 | let app = App(Rc::new(AppInner { pipeline }));
34 | Ok(app)
35 | }
36 |
37 | pub async fn run(&self) -> Result<(), anyhow::Error> {
38 | self.pipeline.prepare()?;
39 | let bin = self.pipeline.pipeline.clone().upcast::();
40 | let mut gw = janus::JanusGateway::new(bin).await?;
41 | self.pipeline.start()?;
42 | gw.run().await?;
43 | Ok(())
44 | }
45 | }
46 |
47 | // Make sure to shut down the pipeline when it goes out of scope
48 | // to release any system resources
49 | impl Drop for AppInner {
50 | fn drop(&mut self) {
51 | let _ = self.pipeline.stop();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/settings.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
4 | pub enum VideoResolution {
5 | V480P,
6 | V720P,
7 | V1080P,
8 | }
9 |
10 | impl Default for VideoResolution {
11 | fn default() -> Self {
12 | VideoResolution::V720P
13 | }
14 | }
15 |
16 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
17 | pub enum WebRTCCodec {
18 | VP8,
19 | VP9,
20 | H264,
21 | }
22 |
23 | impl Default for WebRTCCodec {
24 | fn default() -> Self {
25 | WebRTCCodec::VP8
26 | }
27 | }
28 |
29 | #[derive(Debug)]
30 | pub struct VideoParameter {
31 | pub encoder: &'static str,
32 | pub encoding_name: &'static str,
33 | pub payloader: &'static str,
34 | }
35 |
36 | const VP8_PARAM: VideoParameter = VideoParameter {
37 | encoder: "vp8enc target-bitrate=400000 threads=4 overshoot=25 undershoot=100 deadline=33000 keyframe-max-dist=1",
38 | encoding_name: "VP8",
39 | payloader: "rtpvp8pay picture-id-mode=2"
40 | };
41 |
42 | const VP9_PARAM: VideoParameter = VideoParameter {
43 | encoder: "vp9enc target-bitrate=128000 undershoot=100 deadline=33000 keyframe-max-dist=1",
44 | encoding_name: "VP9",
45 | payloader: "rtpvp9pay picture-id-mode=2",
46 | };
47 |
48 | const H264_PARAM: VideoParameter = VideoParameter {
49 | //encoder: "x264enc tune=zerolatency",
50 | encoder: "vaapih264enc",
51 | encoding_name: "H264",
52 | payloader: "rtph264pay",
53 | };
54 |
55 | #[derive(Deserialize, Serialize, Debug, Clone)]
56 | pub struct Settings {
57 | pub video_resolution: VideoResolution,
58 | pub webrtc_codec: WebRTCCodec,
59 | }
60 |
61 | impl Default for Settings {
62 | fn default() -> Settings {
63 | Settings {
64 | //h264_encoder: "video/x-raw,format=NV12 ! vaapih264enc bitrate=20000 keyframe-period=60 ! video/x-h264,profile=main".to_string(),
65 | video_resolution: VideoResolution::default(),
66 | webrtc_codec: WebRTCCodec::default(),
67 | }
68 | }
69 | }
70 |
71 | impl Settings {
72 | pub fn webrtc_codec_params(&self) -> VideoParameter {
73 | match self.webrtc_codec {
74 | WebRTCCodec::VP8 => VP8_PARAM,
75 | WebRTCCodec::VP9 => VP9_PARAM,
76 | WebRTCCodec::H264 => H264_PARAM,
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GStreamer WPE Web Overlay WebRTC Broadcast demo
2 |
3 | This application allows the live video input (webcam) to be mixed with the
4 | contents of a web page and streamed to a [Janus WebRTC server](https://janus.conf.meetecho.com).
5 |
6 |
7 | ## Installation
8 |
9 | No binary package is provided for this demo yet. For the time being you need to
10 | build it from source.
11 |
12 | The pre-requirements on the publisher side are:
13 |
14 | - NodeJS
15 | - Rust
16 | - GStreamer (including gst-plugins-bad with wpesrc enabled)
17 |
18 | Then you need a Janus instance, running on a remote server. This Janus instance
19 | should have the video-room plugin enabled and the WebSocket transport plugin
20 | enabled. You might need to open the TCP port 8989 and some UDP ports as well, as
21 | required for RTP. Then copy the contents of the janus-web-app to the server HTTP
22 | htdocs directory. We will assume the location of the Janus instance is
23 | https://janus.example.com.
24 |
25 | The next step is to build the publisher app:
26 |
27 | 1. Ensure you have Rust/Cargo installed with [rustup](https://rustup.rs)
28 | 2. Make sure you have GstWPE available on your Linux machine. It is provided by
29 | gst-plugins-bad since version 1.16. This command should show the details of
30 | the plugin: `gst-inspect-1.0 wpesrc`.
31 | 3. Compile the Rust app: `cargo build --release`
32 | 4. Start the Node app: `npm i wpe-graphics-overlays; node wpe-graphics-overlays/server.js`
33 | 5. Open the admin web-ui located at http://127.0.0.1:3000/admin
34 | 6. Start the Rust app: `cargo run --release -- -s wss://janus.example.com:8989 -r 1234 -f 42`
35 |
36 | So the app will connect to Janus over WebSockets and hopefully publish the live
37 | stream in the room 1234, with a feed ID of 42. These values are also referenced
38 | in the webrtc.js file of the janus-web-app.
39 |
40 | You should also see a GTK window popup on your desktop, showing the video
41 | preview. This could be made optional though.
42 |
43 | Finally, more clients can connect to the janus-web-app, to watch the live stream.
44 |
45 | ## Further configuration
46 |
47 | By default the publisher app will encode the video in VP8. You can switch to VP9
48 | or H264 by editing the
49 | `~/.config/com.igalia.gstwpe.broadcast.demo/settings.toml` file. You can also
50 | change the video resolution there.
51 |
52 | ## Credits
53 |
54 | The code is adapted from the [RustFest 2019 Barcelona Rust/GTK/GStreamer workshop app](https://github.com/sdroege/rustfest-barcelona19-gst-webrtc-workshop). Many thanks to Sebastian Dröge !
55 |
56 | The HTML/CSS template is based on the [Pure CSS Horizontal Ticker codepen](https://codepen.io/lewismcarey/pen/GJZVoG).
57 |
58 | The NodeJS app is a fork of the [Roses CasparCG
59 | Graphics](https://github.com/moschopsuk/Roses-2015-CasparCG-Graphics) authored
60 | by Luke Moscrop.
61 |
--------------------------------------------------------------------------------
/janus-web-app/webrtc.js:
--------------------------------------------------------------------------------
1 | var remoteVideo;
2 | var peerConnection;
3 | var janusConnection;
4 | var sessionId;
5 | var handleId;
6 | const roomId = 1234;
7 | const feedId = 42;
8 | const CONFIG = { audio: false, video: false, iceServers: [ {urls: 'stun:stun.l.google.com:19302'}, ] };
9 | const RECEIVERS = { create_session, create_handle, publish, join_subscriber, ack };
10 |
11 | function send(msg) {
12 | janusConnection.send(JSON.stringify(msg));
13 | }
14 |
15 | function ack(payload) {}
16 |
17 | function start() {
18 | remoteVideo = document.getElementById('remoteVideo');
19 | janusConnection = new WebSocket('wss://' + window.location.hostname + ':8989', 'janus-protocol');
20 | janusConnection.onmessage = function(message) {
21 | var payload = JSON.parse(message.data);
22 | var receiver = RECEIVERS[payload.janus] || RECEIVERS[payload.transaction] || console.log;
23 | receiver(payload);
24 | };
25 | janusConnection.onopen = function(event) {
26 | send({janus: 'create', transaction: 'create_session'});
27 | };
28 | }
29 |
30 | function join_broadcast() {
31 | peerConnection = new RTCPeerConnection(CONFIG);
32 | peerConnection.onicecandidate = on_ice_candidate;
33 | peerConnection.ontrack = on_track;
34 |
35 | send({janus: 'message', transaction: 'join_subscriber',
36 | body: {request : 'join', ptype: 'subscriber', room: roomId, feed: feedId},
37 | session_id: sessionId, handle_id: handleId});
38 | }
39 |
40 | function on_ice_candidate(event) {
41 | send({janus: 'trickle', transaction: 'candidate', candidate: event.candidate,
42 | session_id: sessionId, handle_id: handleId});
43 | }
44 |
45 | function on_track(event) {
46 | remoteVideo.srcObject = event.streams[0];
47 | remoteVideo.play();
48 | }
49 |
50 | function keepalive() {
51 | send({janus: 'keepalive', transaction: 'keepalive', session_id: sessionId});
52 | }
53 |
54 | function create_session(payload) {
55 | sessionId = payload.data.id;
56 | setInterval(keepalive, 30000);
57 | send({janus: 'attach', transaction: 'create_handle', plugin: 'janus.plugin.videoroom', session_id: sessionId});
58 | }
59 |
60 | function create_handle(payload) {
61 | handleId = payload.data.id;
62 | join_broadcast();
63 | }
64 |
65 | function publish(payload) {
66 | peerConnection.setRemoteDescription(new RTCSessionDescription(payload.jsep));
67 | }
68 |
69 | function join_subscriber(payload) {
70 | if (!payload.jsep) {
71 | var container = document.getElementById('message');
72 | if (payload.plugindata.data.error_code == 428) {
73 | container.innerHTML = "GstWPE demo is offline. ";
74 | }
75 | container.innerHTML += payload.plugindata.data.error;
76 | return;
77 | }
78 | peerConnection.setRemoteDescription(new RTCSessionDescription(payload.jsep));
79 | peerConnection.createAnswer().then(function(answer) {
80 | peerConnection.setLocalDescription(answer);
81 | send({janus: 'message', transaction: 'blah', body: {request: 'start'},
82 | jsep: answer, session_id: sessionId, handle_id: handleId});
83 | });
84 | }
85 |
--------------------------------------------------------------------------------
/src/pipeline.rs:
--------------------------------------------------------------------------------
1 | use crate::settings::VideoResolution;
2 | use crate::utils;
3 | use gst::{self, prelude::*};
4 | use std::error;
5 | use std::ops;
6 | use std::rc::{Rc, Weak};
7 |
8 | // Our refcounted pipeline struct for containing all the media state we have to carry around.
9 | #[derive(Clone)]
10 | pub struct Pipeline(Rc);
11 |
12 | // Deref into the contained struct to make usage a bit more ergonomic
13 | impl ops::Deref for Pipeline {
14 | type Target = PipelineInner;
15 |
16 | fn deref(&self) -> &PipelineInner {
17 | &*self.0
18 | }
19 | }
20 |
21 | pub struct PipelineInner {
22 | pub pipeline: gst::Pipeline,
23 | }
24 |
25 | // Weak reference to our pipeline struct
26 | //
27 | // Weak references are important to prevent reference cycles. Reference cycles are cases where
28 | // struct A references directly or indirectly struct B, and struct B references struct A again
29 | // while both are using reference counting.
30 | pub struct PipelineWeak(Weak);
31 | impl PipelineWeak {
32 | pub fn upgrade(&self) -> Option {
33 | self.0.upgrade().map(Pipeline)
34 | }
35 | }
36 |
37 | impl Pipeline {
38 | pub fn new() -> Result> {
39 | let settings = utils::load_settings();
40 |
41 | let (width, height) = match settings.video_resolution {
42 | VideoResolution::V480P => (640, 480),
43 | VideoResolution::V720P => (1280, 720),
44 | VideoResolution::V1080P => (1920, 1080),
45 | };
46 |
47 | let pipeline = gst::parse_launch(&format!(
48 | "webrtcbin name=webrtcbin stun-server=stun://stun2.l.google.com:19302 \
49 | glvideomixerelement name=mixer sink_1::zorder=0 sink_1::height={height} sink_1::width={width} \
50 | ! tee name=video-tee ! queue ! gtkglsink enable-last-sample=0 name=sink qos=0 \
51 | wpesrc location=http://127.0.0.1:3000 name=wpesrc draw-background=0 \
52 | ! capsfilter name=wpecaps caps=\"video/x-raw(memory:GLMemory),width={width},height={height},pixel-aspect-ratio=(fraction)1/1\" ! glcolorconvert ! queue ! mixer. \
53 | v4l2src name=videosrc ! capsfilter name=camcaps caps=\"image/jpeg,width={width},height={height},framerate=30/1\" ! queue ! jpegparse ! queue ! jpegdec ! videoconvert ! queue ! glupload ! glcolorconvert
54 | ! queue ! mixer. \
55 | ", width=width, height=height)
56 | )?;
57 |
58 | // Upcast to a gst::Pipeline as the above function could've also returned an arbitrary
59 | // gst::Element if a different string was passed
60 | let pipeline = pipeline
61 | .downcast::()
62 | .expect("Couldn't downcast pipeline");
63 |
64 | // Request that the pipeline forwards us all messages, even those that it would otherwise
65 | // aggregate first
66 | pipeline.set_property_message_forward(true);
67 |
68 | let pipeline = Pipeline(Rc::new(PipelineInner { pipeline }));
69 |
70 | // Install a message handler on the pipeline's bus to catch errors
71 | let bus = pipeline.pipeline.get_bus().expect("Pipeline had no bus");
72 |
73 | // GStreamer is thread-safe and it is possible to attach bus watches from any thread, which
74 | // are then nonetheless called from the main thread. So by default, add_watch() requires
75 | // the passed closure to be Send. We want to pass non-Send values into the closure though.
76 | //
77 | // As we are on the main thread and the closure will be called on the main thread, this
78 | // is actually perfectly fine and safe to do and we can use add_watch_local().
79 | // add_watch_local() would panic if we were not calling it from the main thread.
80 | let pipeline_weak = pipeline.downgrade();
81 | bus.add_watch_local(move |_bus, msg| {
82 | let pipeline = upgrade_weak!(pipeline_weak, glib::Continue(false));
83 |
84 | pipeline.on_pipeline_message(msg);
85 |
86 | glib::Continue(true)
87 | })
88 | .expect("Unable to add bus watch");
89 |
90 | Ok(pipeline)
91 | }
92 |
93 | // Downgrade to a weak reference
94 | pub fn downgrade(&self) -> PipelineWeak {
95 | PipelineWeak(Rc::downgrade(&self.0))
96 | }
97 |
98 | pub fn prepare(&self) -> Result {
99 | let settings = utils::load_settings();
100 | let webrtc_codec = settings.webrtc_codec_params();
101 | let bin_description = &format!(
102 | "queue name=webrtc-vqueue ! gldownload ! videoconvert ! {encoder} ! {payloader} ! queue ! capsfilter name=webrtc-vsink caps=\"application/x-rtp,media=video,encoding-name={encoding_name},payload=96\"",
103 | encoder=webrtc_codec.encoder, payloader=webrtc_codec.payloader,
104 | encoding_name=webrtc_codec.encoding_name
105 | );
106 |
107 | let bin = gst::parse_bin_from_description(bin_description, false).unwrap();
108 | bin.set_name("webrtc-vbin").unwrap();
109 |
110 | let video_queue = bin
111 | .get_by_name("webrtc-vqueue")
112 | .expect("No webrtc-vqueue found");
113 | let video_tee = self
114 | .pipeline
115 | .get_by_name("video-tee")
116 | .expect("No video-tee found");
117 |
118 | self.pipeline
119 | .add(&bin)
120 | .expect("Failed to add recording bin");
121 |
122 | let srcpad = video_tee
123 | .get_request_pad("src_%u")
124 | .expect("Failed to request new pad from tee");
125 | let sinkpad = video_queue
126 | .get_static_pad("sink")
127 | .expect("Failed to get sink pad from recording bin");
128 |
129 | if let Ok(video_ghost_pad) = gst::GhostPad::new(Some("video_sink"), &sinkpad) {
130 | bin.add_pad(&video_ghost_pad).unwrap();
131 | srcpad.link(&video_ghost_pad).unwrap();
132 | }
133 |
134 | let webrtcbin = self.pipeline.get_by_name("webrtcbin").unwrap();
135 | let sinkpad2 = webrtcbin.get_request_pad("sink_%u").unwrap();
136 | let vsink = bin
137 | .get_by_name("webrtc-vsink")
138 | .expect("No webrtc-vqueue found");
139 | let srcpad = vsink.get_static_pad("src").unwrap();
140 | if let Ok(webrtc_ghost_pad) = gst::GhostPad::new(Some("webrtc_video_src"), &srcpad) {
141 | bin.add_pad(&webrtc_ghost_pad).unwrap();
142 | webrtc_ghost_pad.link(&sinkpad2).unwrap();
143 | }
144 |
145 | self.pipeline.set_state(gst::State::Ready)
146 | }
147 |
148 | pub fn start(&self) -> Result {
149 | // This has no effect if called multiple times
150 | self.pipeline.set_state(gst::State::Playing)
151 | }
152 |
153 | pub fn stop(&self) -> Result {
154 | // This has no effect if called multiple times
155 | self.pipeline.set_state(gst::State::Null)
156 | }
157 |
158 | // Here we handle all message we get from the GStreamer pipeline. These are notifications sent
159 | // from GStreamer, including errors that happend at runtime.
160 | //
161 | // This is always called from the main application thread by construction.
162 | fn on_pipeline_message(&self, msg: &gst::MessageRef) {
163 | use gst::MessageView;
164 |
165 | // A message can contain various kinds of information but
166 | // here we are only interested in errors so far
167 | match msg.view() {
168 | MessageView::Error(err) => {
169 | panic!(
170 | "Error from {:?}: {} ({:?})",
171 | err.get_src().map(|s| s.get_path_string()),
172 | err.get_error(),
173 | err.get_debug()
174 | );
175 | }
176 | MessageView::Application(msg) => match msg.get_structure() {
177 | // Here we can send ourselves messages from any thread and show them to the user in
178 | // the UI in case something goes wrong
179 | Some(s) if s.get_name() == "warning" => {
180 | let text = s
181 | .get::<&str>("text")
182 | .expect("Warning message without text")
183 | .unwrap();
184 | panic!("{}", text);
185 | }
186 | _ => (),
187 | },
188 | MessageView::StateChanged(state_changed) => {
189 | if let Some(element) = msg.get_src() {
190 | if element == self.pipeline {
191 | let bin_ref = element.downcast_ref::().unwrap();
192 | let filename = format!(
193 | "gst-wpe-broadcast-demo-{:#?}_to_{:#?}",
194 | state_changed.get_old(),
195 | state_changed.get_current()
196 | );
197 | bin_ref.debug_to_dot_file_with_ts(gst::DebugGraphDetails::all(), filename);
198 | }
199 | }
200 | }
201 | MessageView::AsyncDone(_) => {
202 | if let Some(element) = msg.get_src() {
203 | let bin_ref = element.downcast_ref::().unwrap();
204 | bin_ref.debug_to_dot_file_with_ts(
205 | gst::DebugGraphDetails::all(),
206 | "gst-wpe-broadcast-demo-async-done",
207 | );
208 | }
209 | }
210 | _ => (),
211 | };
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/src/janus.rs:
--------------------------------------------------------------------------------
1 | // GStreamer
2 | //
3 | // Copyright (C) 2018 maxmcd
4 | // Copyright (C) 2019 Sebastian Dröge
5 | // Copyright (C) 2020 Philippe Normand
6 | //
7 | // This library is free software; you can redistribute it and/or
8 | // modify it under the terms of the GNU Library General Public
9 | // License as published by the Free Software Foundation; either
10 | // version 2 of the License, or (at your option) any later version.
11 | //
12 | // This library is distributed in the hope that it will be useful,
13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 | // Library General Public License for more details.
16 | //
17 | // You should have received a copy of the GNU Library General Public
18 | // License along with this library; if not, write to the
19 | // Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 | // Boston, MA 02110-1301, USA.
21 |
22 | use {
23 | anyhow::{anyhow, bail, Context},
24 | async_tungstenite::{gio::connect_async, tungstenite},
25 | futures::channel::mpsc,
26 | futures::sink::{Sink, SinkExt},
27 | futures::stream::{Stream, StreamExt},
28 | gst::gst_element_error,
29 | gst::prelude::*,
30 | http::Request,
31 | rand::prelude::*,
32 | serde_derive::{Deserialize, Serialize},
33 | serde_json::json,
34 | std::sync::{Arc, Mutex, Weak},
35 | structopt::StructOpt,
36 | tungstenite::Message as WsMessage,
37 | };
38 |
39 | #[derive(Debug, StructOpt)]
40 | pub struct Args {
41 | #[structopt(short, long, default_value = "wss://janus.conf.meetecho.com/ws:8989")]
42 | server: String,
43 | #[structopt(short, long, default_value = "1234")]
44 | room_id: u32,
45 | #[structopt(short, long, default_value = "666")]
46 | feed_id: u32,
47 | }
48 |
49 | #[derive(Serialize, Deserialize, Debug)]
50 | struct Base {
51 | janus: String,
52 | transaction: Option,
53 | session_id: Option,
54 | sender: Option,
55 | }
56 |
57 | #[derive(Serialize, Deserialize, Debug)]
58 | struct DataHolder {
59 | id: i64,
60 | }
61 |
62 | #[derive(Serialize, Deserialize, Debug)]
63 | struct PluginDataHolder {
64 | videoroom: String,
65 | room: Option,
66 | #[serde(rename = "current-bitrate")]
67 | current_bitrate: Option,
68 | description: Option,
69 | id: Option,
70 | configured: Option,
71 | video_codec: Option,
72 | unpublished: Option,
73 | }
74 |
75 | #[derive(Serialize, Deserialize, Debug)]
76 | struct PluginHolder {
77 | plugin: String,
78 | data: PluginDataHolder,
79 | }
80 |
81 | #[derive(Serialize, Deserialize, Debug)]
82 | struct IceHolder {
83 | candidate: String,
84 | #[serde(rename = "sdpMLineIndex")]
85 | sdp_mline_index: u32,
86 | }
87 |
88 | #[derive(Serialize, Deserialize, Debug)]
89 | struct JsepHolder {
90 | #[serde(rename = "type")]
91 | type_: String,
92 | sdp: Option,
93 | ice: Option,
94 | }
95 |
96 | #[derive(Serialize, Deserialize, Debug)]
97 | struct JsonReply {
98 | #[serde(flatten)]
99 | base: Base,
100 | data: Option,
101 | #[serde(rename = "plugindata")]
102 | plugin_data: Option,
103 | jsep: Option,
104 | }
105 |
106 | fn transaction_id() -> String {
107 | thread_rng()
108 | .sample_iter(&rand::distributions::Alphanumeric)
109 | .take(30)
110 | .collect()
111 | }
112 |
113 | // Strong reference to the state of one peer
114 | #[derive(Debug, Clone)]
115 | struct Peer(Arc);
116 |
117 | // Weak reference to the state of one peer
118 | #[derive(Debug, Clone)]
119 | struct PeerWeak(Weak);
120 |
121 | impl PeerWeak {
122 | // Try upgrading a weak reference to a strong one
123 | fn upgrade(&self) -> Option {
124 | self.0.upgrade().map(Peer)
125 | }
126 | }
127 |
128 | // To be able to access the Peers's fields directly
129 | impl std::ops::Deref for Peer {
130 | type Target = PeerInner;
131 |
132 | fn deref(&self) -> &PeerInner {
133 | &self.0
134 | }
135 | }
136 |
137 | #[derive(Clone, Copy, Debug)]
138 | struct ConnectionHandle {
139 | id: i64,
140 | session_id: i64,
141 | }
142 |
143 | // Actual peer state
144 | #[derive(Debug)]
145 | struct PeerInner {
146 | handle: ConnectionHandle,
147 | bin: gst::Bin,
148 | webrtcbin: gst::Element,
149 | send_msg_tx: Arc>>,
150 | }
151 |
152 | impl Peer {
153 | // Downgrade the strong reference to a weak reference
154 | fn downgrade(&self) -> PeerWeak {
155 | PeerWeak(Arc::downgrade(&self.0))
156 | }
157 |
158 | // Whenever webrtcbin tells us that (re-)negotiation is needed, simply ask
159 | // for a new offer SDP from webrtcbin without any customization and then
160 | // asynchronously send it to the peer via the WebSocket connection
161 | fn on_negotiation_needed(&self) -> Result<(), anyhow::Error> {
162 | info!("starting negotiation with peer");
163 |
164 | let peer_clone = self.downgrade();
165 | let promise = gst::Promise::new_with_change_func(move |res| {
166 | let s = res.expect("no answer");
167 | let peer = upgrade_weak!(peer_clone);
168 |
169 | if let Err(err) = peer.on_offer_created(&s.to_owned()) {
170 | gst_element_error!(
171 | peer.bin,
172 | gst::LibraryError::Failed,
173 | ("Failed to send SDP offer: {:?}", err)
174 | );
175 | }
176 | });
177 |
178 | self.webrtcbin
179 | .emit("create-offer", &[&None::, &promise])?;
180 |
181 | Ok(())
182 | }
183 |
184 | // Once webrtcbin has create the offer SDP for us, handle it by sending it to the peer via the
185 | // WebSocket connection
186 | fn on_offer_created(&self, reply: &gst::Structure) -> Result<(), anyhow::Error> {
187 | let offer = reply
188 | .get_value("offer")?
189 | .get::()
190 | .expect("Invalid argument")
191 | .expect("Invalid offer");
192 | self.webrtcbin
193 | .emit("set-local-description", &[&offer, &None::])?;
194 |
195 | info!("sending SDP offer to peer: {:?}", offer.get_sdp().as_text());
196 |
197 | let transaction = transaction_id();
198 | let sdp_data = offer.get_sdp().as_text()?;
199 | let msg = WsMessage::Text(
200 | json!({
201 | "janus": "message",
202 | "transaction": transaction,
203 | "session_id": self.handle.session_id,
204 | "handle_id": self.handle.id,
205 | "body": {
206 | "request": "publish",
207 | "audio": true,
208 | "video": true,
209 | },
210 | "jsep": {
211 | "sdp": sdp_data,
212 | "trickle": true,
213 | "type": "offer"
214 | }
215 | })
216 | .to_string(),
217 | );
218 | self.send_msg_tx
219 | .lock()
220 | .expect("Invalid message sender")
221 | .unbounded_send(msg)
222 | .with_context(|| "Failed to send SDP offer".to_string())?;
223 |
224 | Ok(())
225 | }
226 |
227 | // Once webrtcbin has create the answer SDP for us, handle it by sending it to the peer via the
228 | // WebSocket connection
229 | fn on_answer_created(&self, reply: &gst::Structure) -> Result<(), anyhow::Error> {
230 | let answer = reply
231 | .get_value("answer")?
232 | .get::()
233 | .expect("Invalid argument")
234 | .expect("Invalid answer");
235 | self.webrtcbin
236 | .emit("set-local-description", &[&answer, &None::])?;
237 |
238 | info!(
239 | "sending SDP answer to peer: {:?}",
240 | answer.get_sdp().as_text()
241 | );
242 |
243 | Ok(())
244 | }
245 |
246 | // Handle incoming SDP answers from the peer
247 | fn handle_sdp(&self, type_: &str, sdp: &str) -> Result<(), anyhow::Error> {
248 | if type_ == "answer" {
249 | info!("Received answer:\n{}\n", sdp);
250 |
251 | let ret = gst_sdp::SDPMessage::parse_buffer(sdp.as_bytes())
252 | .map_err(|_| anyhow!("Failed to parse SDP answer"))?;
253 | let answer =
254 | gst_webrtc::WebRTCSessionDescription::new(gst_webrtc::WebRTCSDPType::Answer, ret);
255 |
256 | self.webrtcbin
257 | .emit("set-remote-description", &[&answer, &None::])?;
258 |
259 | Ok(())
260 | } else if type_ == "offer" {
261 | info!("Received offer:\n{}\n", sdp);
262 |
263 | let ret = gst_sdp::SDPMessage::parse_buffer(sdp.as_bytes())
264 | .map_err(|_| anyhow!("Failed to parse SDP offer"))?;
265 |
266 | // And then asynchronously start our pipeline and do the next steps. The
267 | // pipeline needs to be started before we can create an answer
268 | let peer_clone = self.downgrade();
269 | self.bin.call_async(move |_pipeline| {
270 | let peer = upgrade_weak!(peer_clone);
271 |
272 | let offer = gst_webrtc::WebRTCSessionDescription::new(
273 | gst_webrtc::WebRTCSDPType::Offer,
274 | ret,
275 | );
276 |
277 | peer.0
278 | .webrtcbin
279 | .emit("set-remote-description", &[&offer, &None::])
280 | .expect("Unable to set remote description");
281 |
282 | let peer_clone = peer.downgrade();
283 | let promise = gst::Promise::new_with_change_func(move |reply| {
284 | let s = reply.expect("No answer");
285 | let peer = upgrade_weak!(peer_clone);
286 |
287 | if let Err(err) = peer.on_answer_created(&s.to_owned()) {
288 | gst_element_error!(
289 | peer.bin,
290 | gst::LibraryError::Failed,
291 | ("Failed to send SDP answer: {:?}", err)
292 | );
293 | }
294 | });
295 |
296 | peer.0
297 | .webrtcbin
298 | .emit("create-answer", &[&None::, &promise])
299 | .expect("Unable to create answer");
300 | });
301 |
302 | Ok(())
303 | } else {
304 | bail!("Sdp type is not \"answer\" but \"{}\"", type_)
305 | }
306 | }
307 |
308 | // Handle incoming ICE candidates from the peer by passing them to webrtcbin
309 | fn handle_ice(&self, sdp_mline_index: u32, candidate: &str) -> Result<(), anyhow::Error> {
310 | info!(
311 | "Received remote ice-candidate {} {}",
312 | sdp_mline_index, candidate
313 | );
314 | self.webrtcbin
315 | .emit("add-ice-candidate", &[&sdp_mline_index, &candidate])?;
316 |
317 | Ok(())
318 | }
319 |
320 | // Asynchronously send ICE candidates to the peer via the WebSocket connection as a JSON
321 | // message
322 | fn on_ice_candidate(&self, mlineindex: u32, candidate: String) -> Result<(), anyhow::Error> {
323 | let transaction = transaction_id();
324 | info!("Sending ICE {} {}", mlineindex, &candidate);
325 | let msg = WsMessage::Text(
326 | json!({
327 | "janus": "trickle",
328 | "transaction": transaction,
329 | "session_id": self.handle.session_id,
330 | "handle_id": self.handle.id,
331 | "candidate": {
332 | "candidate": candidate,
333 | "sdpMLineIndex": mlineindex
334 | },
335 | })
336 | .to_string(),
337 | );
338 | self.send_msg_tx
339 | .lock()
340 | .expect("Invalid message sender")
341 | .unbounded_send(msg)
342 | .with_context(|| "Failed to send ICE candidate".to_string())?;
343 |
344 | Ok(())
345 | }
346 | }
347 |
348 | // At least shut down the bin here if it didn't happen so far
349 | impl Drop for PeerInner {
350 | fn drop(&mut self) {
351 | let _ = self.bin.set_state(gst::State::Null);
352 | }
353 | }
354 |
355 | type WsStream =
356 | std::pin::Pin> + Send>>;
357 | type WsSink = std::pin::Pin + Send>>;
358 |
359 | pub struct JanusGateway {
360 | ws_stream: Option,
361 | ws_sink: Option,
362 | handle: ConnectionHandle,
363 | peer: Mutex,
364 | send_ws_msg_rx: Option>,
365 | }
366 |
367 | impl JanusGateway {
368 | pub async fn new(pipeline: gst::Bin) -> Result {
369 | let args = Args::from_args();
370 | let request = Request::builder()
371 | .uri(&args.server)
372 | .header("Sec-WebSocket-Protocol", "janus-protocol")
373 | .body(())?;
374 |
375 | let (mut ws, _) = connect_async(request).await?;
376 |
377 | let transaction = transaction_id();
378 | let msg = WsMessage::Text(
379 | json!({
380 | "janus": "create",
381 | "transaction": transaction,
382 | })
383 | .to_string(),
384 | );
385 | ws.send(msg).await?;
386 |
387 | let msg = ws
388 | .next()
389 | .await
390 | .ok_or_else(|| anyhow!("didn't receive anything"))??;
391 | let payload = msg.to_text()?;
392 | let json_msg: JsonReply = serde_json::from_str(payload)?;
393 | assert_eq!(json_msg.base.janus, "success");
394 | assert_eq!(json_msg.base.transaction, Some(transaction));
395 | let session_id = json_msg.data.expect("no session id").id;
396 |
397 | let transaction = transaction_id();
398 | let msg = WsMessage::Text(
399 | json!({
400 | "janus": "attach",
401 | "transaction": transaction,
402 | "plugin": "janus.plugin.videoroom",
403 | "session_id": session_id,
404 | })
405 | .to_string(),
406 | );
407 | ws.send(msg).await?;
408 |
409 | let msg = ws
410 | .next()
411 | .await
412 | .ok_or_else(|| anyhow!("didn't receive anything"))??;
413 | let payload = msg.to_text()?;
414 | let json_msg: JsonReply = serde_json::from_str(payload)?;
415 | assert_eq!(json_msg.base.janus, "success");
416 | assert_eq!(json_msg.base.transaction, Some(transaction));
417 | let handle = json_msg.data.expect("no session id").id;
418 |
419 | let transaction = transaction_id();
420 | let msg = WsMessage::Text(
421 | json!({
422 | "janus": "message",
423 | "transaction": transaction,
424 | "session_id": session_id,
425 | "handle_id": handle,
426 | "body": {
427 | "request": "join",
428 | "ptype": "publisher",
429 | "room": args.room_id,
430 | "id": args.feed_id,
431 | },
432 | })
433 | .to_string(),
434 | );
435 | ws.send(msg).await?;
436 |
437 | let webrtcbin = pipeline
438 | .get_by_name("webrtcbin")
439 | .expect("can't find webrtcbin");
440 |
441 | if let Ok(transceiver) = webrtcbin.emit("get-transceiver", &[&0.to_value()]) {
442 | if let Some(t) = transceiver {
443 | if let Ok(obj) = t.get::() {
444 | obj.expect("Invalid transceiver")
445 | .set_property("do-nack", &true.to_value())?;
446 | }
447 | }
448 | }
449 |
450 | let (send_ws_msg_tx, send_ws_msg_rx) = mpsc::unbounded::();
451 |
452 | let connection_handle = ConnectionHandle {
453 | id: handle,
454 | session_id,
455 | };
456 |
457 | let peer = Peer(Arc::new(PeerInner {
458 | handle: connection_handle,
459 | bin: pipeline,
460 | webrtcbin,
461 | send_msg_tx: Arc::new(Mutex::new(send_ws_msg_tx)),
462 | }));
463 |
464 | // Connect to on-negotiation-needed to handle sending an Offer
465 | let peer_clone = peer.downgrade();
466 | peer.webrtcbin
467 | .connect("on-negotiation-needed", false, move |_| {
468 | let peer = upgrade_weak!(peer_clone, None);
469 | if let Err(err) = peer.on_negotiation_needed() {
470 | gst_element_error!(
471 | peer.bin,
472 | gst::LibraryError::Failed,
473 | ("Failed to negotiate: {:?}", err)
474 | );
475 | }
476 |
477 | None
478 | })?;
479 |
480 | // Whenever there is a new ICE candidate, send it to the peer
481 | let peer_clone = peer.downgrade();
482 | peer.webrtcbin
483 | .connect("on-ice-candidate", false, move |values| {
484 | let mlineindex = values[1]
485 | .get::()
486 | .expect("Invalid argument")
487 | .expect("Invalid type");
488 | let candidate = values[2]
489 | .get::()
490 | .expect("Invalid argument")
491 | .expect("Invalid type");
492 |
493 | let peer = upgrade_weak!(peer_clone, None);
494 | if let Err(err) = peer.on_ice_candidate(mlineindex, candidate) {
495 | gst_element_error!(
496 | peer.bin,
497 | gst::LibraryError::Failed,
498 | ("Failed to send ICE candidate: {:?}", err)
499 | );
500 | }
501 |
502 | None
503 | })?;
504 |
505 | // Split the websocket into the Sink and Stream
506 | let (ws_sink, ws_stream) = ws.split();
507 |
508 | Ok(Self {
509 | ws_stream: Some(ws_stream.boxed()),
510 | ws_sink: Some(Box::pin(ws_sink)),
511 | handle: connection_handle,
512 | peer: Mutex::new(peer),
513 | send_ws_msg_rx: Some(send_ws_msg_rx),
514 | })
515 | }
516 |
517 | pub async fn run(&mut self) -> Result<(), anyhow::Error> {
518 | if let Some(ws_stream) = self.ws_stream.take() {
519 | // Fuse the Stream, required for the select macro
520 | let mut ws_stream = ws_stream.fuse();
521 |
522 | // Channel for outgoing WebSocket messages from other threads
523 | let send_ws_msg_rx = self
524 | .send_ws_msg_rx
525 | .take()
526 | .expect("Invalid message receiver");
527 | let mut send_ws_msg_rx = send_ws_msg_rx.fuse();
528 |
529 | let timer = glib::interval_stream(10_000);
530 | let mut timer_fuse = timer.fuse();
531 |
532 | let mut sink = self.ws_sink.take().expect("Invalid websocket sink");
533 | loop {
534 | let ws_msg = futures::select! {
535 | // Handle the WebSocket messages here
536 | ws_msg = ws_stream.select_next_some() => {
537 | match ws_msg? {
538 | WsMessage::Close(_) => {
539 | info!("peer disconnected");
540 | break
541 | },
542 | WsMessage::Ping(data) => Some(WsMessage::Pong(data)),
543 | WsMessage::Pong(_) => None,
544 | WsMessage::Binary(_) => None,
545 | WsMessage::Text(text) => {
546 | if let Err(err) = self.handle_websocket_message(&text) {
547 | error!("Failed to parse message: {} ... error: {}", &text, err);
548 | }
549 | None
550 | },
551 | }
552 | },
553 | // Handle WebSocket messages we created asynchronously
554 | // to send them out now
555 | ws_msg = send_ws_msg_rx.select_next_some() => Some(ws_msg),
556 |
557 | // Handle keepalive ticks, fired every 10 seconds
558 | ws_msg = timer_fuse.select_next_some() => {
559 | let transaction = transaction_id();
560 | Some(WsMessage::Text(
561 | json!({
562 | "janus": "keepalive",
563 | "transaction": transaction,
564 | "handle_id": self.handle.id,
565 | "session_id": self.handle.session_id,
566 | }).to_string(),
567 | ))
568 | },
569 | // Once we're done, break the loop and return
570 | complete => break,
571 | };
572 |
573 | // If there's a message to send out, do so now
574 | if let Some(ws_msg) = ws_msg {
575 | sink.send(ws_msg).await?;
576 | }
577 | }
578 | }
579 | Ok(())
580 | }
581 |
582 | fn handle_jsep(&self, jsep: &JsepHolder) -> Result<(), anyhow::Error> {
583 | if let Some(sdp) = &jsep.sdp {
584 | assert_eq!(jsep.type_, "answer");
585 | let peer = self.peer.lock().expect("Invalid peer");
586 | return peer.handle_sdp(&jsep.type_, &sdp);
587 | } else if let Some(ice) = &jsep.ice {
588 | let peer = self.peer.lock().expect("Invalid peer");
589 | return peer.handle_ice(ice.sdp_mline_index, &ice.candidate);
590 | }
591 |
592 | Ok(())
593 | }
594 |
595 | // Handle WebSocket messages, both our own as well as WebSocket protocol messages
596 | fn handle_websocket_message(&self, msg: &str) -> Result<(), anyhow::Error> {
597 | trace!("Incoming raw message: {}", msg);
598 | let json_msg: JsonReply = serde_json::from_str(msg)?;
599 | let payload_type = &json_msg.base.janus;
600 | if payload_type == "ack" {
601 | trace!(
602 | "Ack transaction {:#?}, sessionId {:#?}",
603 | json_msg.base.transaction,
604 | json_msg.base.session_id
605 | );
606 | } else {
607 | debug!("Incoming JSON WebSocket message: {:#?}", json_msg);
608 | }
609 | if payload_type == "event" {
610 | if let Some(_plugin_data) = json_msg.plugin_data {
611 | if let Some(jsep) = json_msg.jsep {
612 | return self.handle_jsep(&jsep);
613 | }
614 | }
615 | }
616 | Ok(())
617 | }
618 | }
619 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | [[package]]
4 | name = "addr2line"
5 | version = "0.12.2"
6 | source = "registry+https://github.com/rust-lang/crates.io-index"
7 | checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c"
8 | dependencies = [
9 | "gimli",
10 | ]
11 |
12 | [[package]]
13 | name = "adler32"
14 | version = "1.1.0"
15 | source = "registry+https://github.com/rust-lang/crates.io-index"
16 | checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
17 |
18 | [[package]]
19 | name = "aho-corasick"
20 | version = "0.7.13"
21 | source = "registry+https://github.com/rust-lang/crates.io-index"
22 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
23 | dependencies = [
24 | "memchr",
25 | ]
26 |
27 | [[package]]
28 | name = "anyhow"
29 | version = "1.0.31"
30 | source = "registry+https://github.com/rust-lang/crates.io-index"
31 | checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
32 |
33 | [[package]]
34 | name = "async-tungstenite"
35 | version = "0.7.0"
36 | source = "registry+https://github.com/rust-lang/crates.io-index"
37 | checksum = "3c35882200d230428ae9cf74acb72aaa9c0a912911b4dad8fb890da49608e127"
38 | dependencies = [
39 | "futures-io",
40 | "futures-util",
41 | "gio",
42 | "glib",
43 | "log 0.4.8",
44 | "pin-project",
45 | "tungstenite",
46 | ]
47 |
48 | [[package]]
49 | name = "atty"
50 | version = "0.2.14"
51 | source = "registry+https://github.com/rust-lang/crates.io-index"
52 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
53 | dependencies = [
54 | "hermit-abi",
55 | "libc",
56 | "winapi",
57 | ]
58 |
59 | [[package]]
60 | name = "autocfg"
61 | version = "1.0.0"
62 | source = "registry+https://github.com/rust-lang/crates.io-index"
63 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
64 |
65 | [[package]]
66 | name = "backtrace"
67 | version = "0.3.49"
68 | source = "registry+https://github.com/rust-lang/crates.io-index"
69 | checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c"
70 | dependencies = [
71 | "addr2line",
72 | "cfg-if",
73 | "libc",
74 | "miniz_oxide",
75 | "object",
76 | "rustc-demangle",
77 | ]
78 |
79 | [[package]]
80 | name = "base64"
81 | version = "0.9.3"
82 | source = "registry+https://github.com/rust-lang/crates.io-index"
83 | checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
84 | dependencies = [
85 | "byteorder",
86 | "safemem",
87 | ]
88 |
89 | [[package]]
90 | name = "base64"
91 | version = "0.11.0"
92 | source = "registry+https://github.com/rust-lang/crates.io-index"
93 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
94 |
95 | [[package]]
96 | name = "base64"
97 | version = "0.12.3"
98 | source = "registry+https://github.com/rust-lang/crates.io-index"
99 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
100 |
101 | [[package]]
102 | name = "bitflags"
103 | version = "0.9.1"
104 | source = "registry+https://github.com/rust-lang/crates.io-index"
105 | checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
106 |
107 | [[package]]
108 | name = "bitflags"
109 | version = "1.2.1"
110 | source = "registry+https://github.com/rust-lang/crates.io-index"
111 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
112 |
113 | [[package]]
114 | name = "block-buffer"
115 | version = "0.7.3"
116 | source = "registry+https://github.com/rust-lang/crates.io-index"
117 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
118 | dependencies = [
119 | "block-padding",
120 | "byte-tools",
121 | "byteorder",
122 | "generic-array",
123 | ]
124 |
125 | [[package]]
126 | name = "block-padding"
127 | version = "0.1.5"
128 | source = "registry+https://github.com/rust-lang/crates.io-index"
129 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
130 | dependencies = [
131 | "byte-tools",
132 | ]
133 |
134 | [[package]]
135 | name = "byte-tools"
136 | version = "0.3.1"
137 | source = "registry+https://github.com/rust-lang/crates.io-index"
138 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
139 |
140 | [[package]]
141 | name = "byteorder"
142 | version = "1.3.4"
143 | source = "registry+https://github.com/rust-lang/crates.io-index"
144 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
145 |
146 | [[package]]
147 | name = "bytes"
148 | version = "0.5.5"
149 | source = "registry+https://github.com/rust-lang/crates.io-index"
150 | checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b"
151 | dependencies = [
152 | "loom",
153 | ]
154 |
155 | [[package]]
156 | name = "cc"
157 | version = "1.0.56"
158 | source = "registry+https://github.com/rust-lang/crates.io-index"
159 | checksum = "77c1f1d60091c1b73e2b1f4560ab419204b178e625fa945ded7b660becd2bd46"
160 |
161 | [[package]]
162 | name = "cfg-if"
163 | version = "0.1.10"
164 | source = "registry+https://github.com/rust-lang/crates.io-index"
165 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
166 |
167 | [[package]]
168 | name = "clap"
169 | version = "2.33.1"
170 | source = "registry+https://github.com/rust-lang/crates.io-index"
171 | checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
172 | dependencies = [
173 | "bitflags 1.2.1",
174 | "textwrap",
175 | "unicode-width",
176 | ]
177 |
178 | [[package]]
179 | name = "digest"
180 | version = "0.8.1"
181 | source = "registry+https://github.com/rust-lang/crates.io-index"
182 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
183 | dependencies = [
184 | "generic-array",
185 | ]
186 |
187 | [[package]]
188 | name = "dtoa"
189 | version = "0.4.6"
190 | source = "registry+https://github.com/rust-lang/crates.io-index"
191 | checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
192 |
193 | [[package]]
194 | name = "embedded-gst-wpe-demo"
195 | version = "0.1.0"
196 | dependencies = [
197 | "anyhow",
198 | "async-tungstenite",
199 | "base64 0.11.0",
200 | "env_logger",
201 | "futures",
202 | "gio",
203 | "glib",
204 | "gstreamer",
205 | "gstreamer-sdp",
206 | "gstreamer-webrtc",
207 | "http",
208 | "log 0.4.8",
209 | "num",
210 | "rand",
211 | "serde",
212 | "serde_any",
213 | "serde_derive",
214 | "serde_json",
215 | "strfmt",
216 | "structopt",
217 | "url 2.1.1",
218 | ]
219 |
220 | [[package]]
221 | name = "env_logger"
222 | version = "0.7.1"
223 | source = "registry+https://github.com/rust-lang/crates.io-index"
224 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
225 | dependencies = [
226 | "atty",
227 | "humantime",
228 | "log 0.4.8",
229 | "regex",
230 | "termcolor",
231 | ]
232 |
233 | [[package]]
234 | name = "error-chain"
235 | version = "0.10.0"
236 | source = "registry+https://github.com/rust-lang/crates.io-index"
237 | checksum = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8"
238 | dependencies = [
239 | "backtrace",
240 | ]
241 |
242 | [[package]]
243 | name = "failure"
244 | version = "0.1.8"
245 | source = "registry+https://github.com/rust-lang/crates.io-index"
246 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
247 | dependencies = [
248 | "backtrace",
249 | "failure_derive",
250 | ]
251 |
252 | [[package]]
253 | name = "failure_derive"
254 | version = "0.1.8"
255 | source = "registry+https://github.com/rust-lang/crates.io-index"
256 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
257 | dependencies = [
258 | "proc-macro2",
259 | "quote",
260 | "syn",
261 | "synstructure",
262 | ]
263 |
264 | [[package]]
265 | name = "fake-simd"
266 | version = "0.1.2"
267 | source = "registry+https://github.com/rust-lang/crates.io-index"
268 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
269 |
270 | [[package]]
271 | name = "fnv"
272 | version = "1.0.7"
273 | source = "registry+https://github.com/rust-lang/crates.io-index"
274 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
275 |
276 | [[package]]
277 | name = "futures"
278 | version = "0.3.5"
279 | source = "registry+https://github.com/rust-lang/crates.io-index"
280 | checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
281 | dependencies = [
282 | "futures-channel",
283 | "futures-core",
284 | "futures-executor",
285 | "futures-io",
286 | "futures-sink",
287 | "futures-task",
288 | "futures-util",
289 | ]
290 |
291 | [[package]]
292 | name = "futures-channel"
293 | version = "0.3.5"
294 | source = "registry+https://github.com/rust-lang/crates.io-index"
295 | checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
296 | dependencies = [
297 | "futures-core",
298 | "futures-sink",
299 | ]
300 |
301 | [[package]]
302 | name = "futures-core"
303 | version = "0.3.5"
304 | source = "registry+https://github.com/rust-lang/crates.io-index"
305 | checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
306 |
307 | [[package]]
308 | name = "futures-executor"
309 | version = "0.3.5"
310 | source = "registry+https://github.com/rust-lang/crates.io-index"
311 | checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
312 | dependencies = [
313 | "futures-core",
314 | "futures-task",
315 | "futures-util",
316 | ]
317 |
318 | [[package]]
319 | name = "futures-io"
320 | version = "0.3.5"
321 | source = "registry+https://github.com/rust-lang/crates.io-index"
322 | checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
323 |
324 | [[package]]
325 | name = "futures-macro"
326 | version = "0.3.5"
327 | source = "registry+https://github.com/rust-lang/crates.io-index"
328 | checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39"
329 | dependencies = [
330 | "proc-macro-hack",
331 | "proc-macro2",
332 | "quote",
333 | "syn",
334 | ]
335 |
336 | [[package]]
337 | name = "futures-sink"
338 | version = "0.3.5"
339 | source = "registry+https://github.com/rust-lang/crates.io-index"
340 | checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc"
341 |
342 | [[package]]
343 | name = "futures-task"
344 | version = "0.3.5"
345 | source = "registry+https://github.com/rust-lang/crates.io-index"
346 | checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626"
347 | dependencies = [
348 | "once_cell",
349 | ]
350 |
351 | [[package]]
352 | name = "futures-util"
353 | version = "0.3.5"
354 | source = "registry+https://github.com/rust-lang/crates.io-index"
355 | checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6"
356 | dependencies = [
357 | "futures-channel",
358 | "futures-core",
359 | "futures-io",
360 | "futures-macro",
361 | "futures-sink",
362 | "futures-task",
363 | "memchr",
364 | "pin-project",
365 | "pin-utils",
366 | "proc-macro-hack",
367 | "proc-macro-nested",
368 | "slab",
369 | ]
370 |
371 | [[package]]
372 | name = "generator"
373 | version = "0.6.21"
374 | source = "registry+https://github.com/rust-lang/crates.io-index"
375 | checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68"
376 | dependencies = [
377 | "cc",
378 | "libc",
379 | "log 0.4.8",
380 | "rustc_version",
381 | "winapi",
382 | ]
383 |
384 | [[package]]
385 | name = "generic-array"
386 | version = "0.12.3"
387 | source = "registry+https://github.com/rust-lang/crates.io-index"
388 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
389 | dependencies = [
390 | "typenum",
391 | ]
392 |
393 | [[package]]
394 | name = "getrandom"
395 | version = "0.1.14"
396 | source = "registry+https://github.com/rust-lang/crates.io-index"
397 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
398 | dependencies = [
399 | "cfg-if",
400 | "libc",
401 | "wasi",
402 | ]
403 |
404 | [[package]]
405 | name = "gimli"
406 | version = "0.21.0"
407 | source = "registry+https://github.com/rust-lang/crates.io-index"
408 | checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c"
409 |
410 | [[package]]
411 | name = "gio"
412 | version = "0.8.1"
413 | source = "registry+https://github.com/rust-lang/crates.io-index"
414 | checksum = "0cd10f9415cce39b53f8024bf39a21f84f8157afa52da53837b102e585a296a5"
415 | dependencies = [
416 | "bitflags 1.2.1",
417 | "futures-channel",
418 | "futures-core",
419 | "futures-io",
420 | "futures-util",
421 | "gio-sys",
422 | "glib",
423 | "glib-sys",
424 | "gobject-sys",
425 | "lazy_static",
426 | "libc",
427 | ]
428 |
429 | [[package]]
430 | name = "gio-sys"
431 | version = "0.9.1"
432 | source = "registry+https://github.com/rust-lang/crates.io-index"
433 | checksum = "4fad225242b9eae7ec8a063bb86974aca56885014672375e5775dc0ea3533911"
434 | dependencies = [
435 | "glib-sys",
436 | "gobject-sys",
437 | "libc",
438 | "pkg-config",
439 | ]
440 |
441 | [[package]]
442 | name = "glib"
443 | version = "0.9.3"
444 | source = "registry+https://github.com/rust-lang/crates.io-index"
445 | checksum = "40fb573a09841b6386ddf15fd4bc6655b4f5b106ca962f57ecaecde32a0061c0"
446 | dependencies = [
447 | "bitflags 1.2.1",
448 | "futures-channel",
449 | "futures-core",
450 | "futures-executor",
451 | "futures-task",
452 | "futures-util",
453 | "glib-sys",
454 | "gobject-sys",
455 | "lazy_static",
456 | "libc",
457 | ]
458 |
459 | [[package]]
460 | name = "glib-sys"
461 | version = "0.9.1"
462 | source = "registry+https://github.com/rust-lang/crates.io-index"
463 | checksum = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2"
464 | dependencies = [
465 | "libc",
466 | "pkg-config",
467 | ]
468 |
469 | [[package]]
470 | name = "gobject-sys"
471 | version = "0.9.1"
472 | source = "registry+https://github.com/rust-lang/crates.io-index"
473 | checksum = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9"
474 | dependencies = [
475 | "glib-sys",
476 | "libc",
477 | "pkg-config",
478 | ]
479 |
480 | [[package]]
481 | name = "gstreamer"
482 | version = "0.15.7"
483 | source = "registry+https://github.com/rust-lang/crates.io-index"
484 | checksum = "ce8664a114cd6ec16bece783d5eee59496919915b1f6884400ba4a953274a163"
485 | dependencies = [
486 | "bitflags 1.2.1",
487 | "cfg-if",
488 | "futures-channel",
489 | "futures-core",
490 | "futures-util",
491 | "glib",
492 | "glib-sys",
493 | "gobject-sys",
494 | "gstreamer-sys",
495 | "lazy_static",
496 | "libc",
497 | "muldiv",
498 | "num-rational",
499 | "paste",
500 | ]
501 |
502 | [[package]]
503 | name = "gstreamer-sdp"
504 | version = "0.15.6"
505 | source = "registry+https://github.com/rust-lang/crates.io-index"
506 | checksum = "547b3b0eb9e01e13ab5cc066c817e2ab758f83790145f80f62d3c8e43c2966af"
507 | dependencies = [
508 | "glib",
509 | "glib-sys",
510 | "gobject-sys",
511 | "gstreamer",
512 | "gstreamer-sdp-sys",
513 | "gstreamer-sys",
514 | ]
515 |
516 | [[package]]
517 | name = "gstreamer-sdp-sys"
518 | version = "0.8.1"
519 | source = "registry+https://github.com/rust-lang/crates.io-index"
520 | checksum = "99e88ac4f9f20323ef3409dddcea3bbf58364ff8eea10b14da5303bfcb23347a"
521 | dependencies = [
522 | "glib-sys",
523 | "gobject-sys",
524 | "gstreamer-sys",
525 | "libc",
526 | "pkg-config",
527 | ]
528 |
529 | [[package]]
530 | name = "gstreamer-sys"
531 | version = "0.8.1"
532 | source = "registry+https://github.com/rust-lang/crates.io-index"
533 | checksum = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db"
534 | dependencies = [
535 | "glib-sys",
536 | "gobject-sys",
537 | "libc",
538 | "pkg-config",
539 | ]
540 |
541 | [[package]]
542 | name = "gstreamer-webrtc"
543 | version = "0.15.5"
544 | source = "registry+https://github.com/rust-lang/crates.io-index"
545 | checksum = "1f433d1294266fb1d65e1dc2d4de365f7f4caf23cb72db3a3bd6904eeec88cf1"
546 | dependencies = [
547 | "glib",
548 | "glib-sys",
549 | "gobject-sys",
550 | "gstreamer",
551 | "gstreamer-sdp",
552 | "gstreamer-sys",
553 | "gstreamer-webrtc-sys",
554 | "libc",
555 | ]
556 |
557 | [[package]]
558 | name = "gstreamer-webrtc-sys"
559 | version = "0.8.1"
560 | source = "registry+https://github.com/rust-lang/crates.io-index"
561 | checksum = "f392bd821b42efecfc21016c8ef20da188b45a45bbb5ddf81758704f93aae615"
562 | dependencies = [
563 | "glib-sys",
564 | "gobject-sys",
565 | "gstreamer-sdp-sys",
566 | "gstreamer-sys",
567 | "libc",
568 | "pkg-config",
569 | ]
570 |
571 | [[package]]
572 | name = "heck"
573 | version = "0.3.1"
574 | source = "registry+https://github.com/rust-lang/crates.io-index"
575 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
576 | dependencies = [
577 | "unicode-segmentation",
578 | ]
579 |
580 | [[package]]
581 | name = "hermit-abi"
582 | version = "0.1.14"
583 | source = "registry+https://github.com/rust-lang/crates.io-index"
584 | checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
585 | dependencies = [
586 | "libc",
587 | ]
588 |
589 | [[package]]
590 | name = "http"
591 | version = "0.2.1"
592 | source = "registry+https://github.com/rust-lang/crates.io-index"
593 | checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
594 | dependencies = [
595 | "bytes",
596 | "fnv",
597 | "itoa",
598 | ]
599 |
600 | [[package]]
601 | name = "httparse"
602 | version = "1.3.4"
603 | source = "registry+https://github.com/rust-lang/crates.io-index"
604 | checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
605 |
606 | [[package]]
607 | name = "humantime"
608 | version = "1.3.0"
609 | source = "registry+https://github.com/rust-lang/crates.io-index"
610 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
611 | dependencies = [
612 | "quick-error",
613 | ]
614 |
615 | [[package]]
616 | name = "idna"
617 | version = "0.1.5"
618 | source = "registry+https://github.com/rust-lang/crates.io-index"
619 | checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
620 | dependencies = [
621 | "matches",
622 | "unicode-bidi",
623 | "unicode-normalization",
624 | ]
625 |
626 | [[package]]
627 | name = "idna"
628 | version = "0.2.0"
629 | source = "registry+https://github.com/rust-lang/crates.io-index"
630 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
631 | dependencies = [
632 | "matches",
633 | "unicode-bidi",
634 | "unicode-normalization",
635 | ]
636 |
637 | [[package]]
638 | name = "input_buffer"
639 | version = "0.3.1"
640 | source = "registry+https://github.com/rust-lang/crates.io-index"
641 | checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754"
642 | dependencies = [
643 | "bytes",
644 | ]
645 |
646 | [[package]]
647 | name = "itoa"
648 | version = "0.4.6"
649 | source = "registry+https://github.com/rust-lang/crates.io-index"
650 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
651 |
652 | [[package]]
653 | name = "lazy_static"
654 | version = "1.4.0"
655 | source = "registry+https://github.com/rust-lang/crates.io-index"
656 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
657 |
658 | [[package]]
659 | name = "libc"
660 | version = "0.2.71"
661 | source = "registry+https://github.com/rust-lang/crates.io-index"
662 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
663 |
664 | [[package]]
665 | name = "linked-hash-map"
666 | version = "0.5.3"
667 | source = "registry+https://github.com/rust-lang/crates.io-index"
668 | checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
669 |
670 | [[package]]
671 | name = "log"
672 | version = "0.3.9"
673 | source = "registry+https://github.com/rust-lang/crates.io-index"
674 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
675 | dependencies = [
676 | "log 0.4.8",
677 | ]
678 |
679 | [[package]]
680 | name = "log"
681 | version = "0.4.8"
682 | source = "registry+https://github.com/rust-lang/crates.io-index"
683 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
684 | dependencies = [
685 | "cfg-if",
686 | ]
687 |
688 | [[package]]
689 | name = "loom"
690 | version = "0.3.4"
691 | source = "registry+https://github.com/rust-lang/crates.io-index"
692 | checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7"
693 | dependencies = [
694 | "cfg-if",
695 | "generator",
696 | "scoped-tls",
697 | ]
698 |
699 | [[package]]
700 | name = "matches"
701 | version = "0.1.8"
702 | source = "registry+https://github.com/rust-lang/crates.io-index"
703 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
704 |
705 | [[package]]
706 | name = "memchr"
707 | version = "2.3.3"
708 | source = "registry+https://github.com/rust-lang/crates.io-index"
709 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
710 |
711 | [[package]]
712 | name = "miniz_oxide"
713 | version = "0.3.7"
714 | source = "registry+https://github.com/rust-lang/crates.io-index"
715 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
716 | dependencies = [
717 | "adler32",
718 | ]
719 |
720 | [[package]]
721 | name = "muldiv"
722 | version = "0.2.1"
723 | source = "registry+https://github.com/rust-lang/crates.io-index"
724 | checksum = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204"
725 |
726 | [[package]]
727 | name = "num"
728 | version = "0.2.1"
729 | source = "registry+https://github.com/rust-lang/crates.io-index"
730 | checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
731 | dependencies = [
732 | "num-bigint",
733 | "num-complex",
734 | "num-integer",
735 | "num-iter",
736 | "num-rational",
737 | "num-traits",
738 | ]
739 |
740 | [[package]]
741 | name = "num-bigint"
742 | version = "0.2.6"
743 | source = "registry+https://github.com/rust-lang/crates.io-index"
744 | checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
745 | dependencies = [
746 | "autocfg",
747 | "num-integer",
748 | "num-traits",
749 | ]
750 |
751 | [[package]]
752 | name = "num-complex"
753 | version = "0.2.4"
754 | source = "registry+https://github.com/rust-lang/crates.io-index"
755 | checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
756 | dependencies = [
757 | "autocfg",
758 | "num-traits",
759 | ]
760 |
761 | [[package]]
762 | name = "num-integer"
763 | version = "0.1.43"
764 | source = "registry+https://github.com/rust-lang/crates.io-index"
765 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
766 | dependencies = [
767 | "autocfg",
768 | "num-traits",
769 | ]
770 |
771 | [[package]]
772 | name = "num-iter"
773 | version = "0.1.41"
774 | source = "registry+https://github.com/rust-lang/crates.io-index"
775 | checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
776 | dependencies = [
777 | "autocfg",
778 | "num-integer",
779 | "num-traits",
780 | ]
781 |
782 | [[package]]
783 | name = "num-rational"
784 | version = "0.2.4"
785 | source = "registry+https://github.com/rust-lang/crates.io-index"
786 | checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
787 | dependencies = [
788 | "autocfg",
789 | "num-bigint",
790 | "num-integer",
791 | "num-traits",
792 | ]
793 |
794 | [[package]]
795 | name = "num-traits"
796 | version = "0.2.12"
797 | source = "registry+https://github.com/rust-lang/crates.io-index"
798 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
799 | dependencies = [
800 | "autocfg",
801 | ]
802 |
803 | [[package]]
804 | name = "object"
805 | version = "0.20.0"
806 | source = "registry+https://github.com/rust-lang/crates.io-index"
807 | checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
808 |
809 | [[package]]
810 | name = "once_cell"
811 | version = "1.4.0"
812 | source = "registry+https://github.com/rust-lang/crates.io-index"
813 | checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
814 |
815 | [[package]]
816 | name = "opaque-debug"
817 | version = "0.2.3"
818 | source = "registry+https://github.com/rust-lang/crates.io-index"
819 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
820 |
821 | [[package]]
822 | name = "paste"
823 | version = "0.1.18"
824 | source = "registry+https://github.com/rust-lang/crates.io-index"
825 | checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
826 | dependencies = [
827 | "paste-impl",
828 | "proc-macro-hack",
829 | ]
830 |
831 | [[package]]
832 | name = "paste-impl"
833 | version = "0.1.18"
834 | source = "registry+https://github.com/rust-lang/crates.io-index"
835 | checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
836 | dependencies = [
837 | "proc-macro-hack",
838 | ]
839 |
840 | [[package]]
841 | name = "percent-encoding"
842 | version = "1.0.1"
843 | source = "registry+https://github.com/rust-lang/crates.io-index"
844 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
845 |
846 | [[package]]
847 | name = "percent-encoding"
848 | version = "2.1.0"
849 | source = "registry+https://github.com/rust-lang/crates.io-index"
850 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
851 |
852 | [[package]]
853 | name = "pin-project"
854 | version = "0.4.22"
855 | source = "registry+https://github.com/rust-lang/crates.io-index"
856 | checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17"
857 | dependencies = [
858 | "pin-project-internal",
859 | ]
860 |
861 | [[package]]
862 | name = "pin-project-internal"
863 | version = "0.4.22"
864 | source = "registry+https://github.com/rust-lang/crates.io-index"
865 | checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7"
866 | dependencies = [
867 | "proc-macro2",
868 | "quote",
869 | "syn",
870 | ]
871 |
872 | [[package]]
873 | name = "pin-utils"
874 | version = "0.1.0"
875 | source = "registry+https://github.com/rust-lang/crates.io-index"
876 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
877 |
878 | [[package]]
879 | name = "pkg-config"
880 | version = "0.3.17"
881 | source = "registry+https://github.com/rust-lang/crates.io-index"
882 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
883 |
884 | [[package]]
885 | name = "ppv-lite86"
886 | version = "0.2.8"
887 | source = "registry+https://github.com/rust-lang/crates.io-index"
888 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
889 |
890 | [[package]]
891 | name = "proc-macro-error"
892 | version = "1.0.3"
893 | source = "registry+https://github.com/rust-lang/crates.io-index"
894 | checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880"
895 | dependencies = [
896 | "proc-macro-error-attr",
897 | "proc-macro2",
898 | "quote",
899 | "syn",
900 | "version_check",
901 | ]
902 |
903 | [[package]]
904 | name = "proc-macro-error-attr"
905 | version = "1.0.3"
906 | source = "registry+https://github.com/rust-lang/crates.io-index"
907 | checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50"
908 | dependencies = [
909 | "proc-macro2",
910 | "quote",
911 | "syn",
912 | "syn-mid",
913 | "version_check",
914 | ]
915 |
916 | [[package]]
917 | name = "proc-macro-hack"
918 | version = "0.5.16"
919 | source = "registry+https://github.com/rust-lang/crates.io-index"
920 | checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
921 |
922 | [[package]]
923 | name = "proc-macro-nested"
924 | version = "0.1.6"
925 | source = "registry+https://github.com/rust-lang/crates.io-index"
926 | checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
927 |
928 | [[package]]
929 | name = "proc-macro2"
930 | version = "1.0.18"
931 | source = "registry+https://github.com/rust-lang/crates.io-index"
932 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
933 | dependencies = [
934 | "unicode-xid",
935 | ]
936 |
937 | [[package]]
938 | name = "quick-error"
939 | version = "1.2.3"
940 | source = "registry+https://github.com/rust-lang/crates.io-index"
941 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
942 |
943 | [[package]]
944 | name = "quote"
945 | version = "1.0.7"
946 | source = "registry+https://github.com/rust-lang/crates.io-index"
947 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
948 | dependencies = [
949 | "proc-macro2",
950 | ]
951 |
952 | [[package]]
953 | name = "rand"
954 | version = "0.7.3"
955 | source = "registry+https://github.com/rust-lang/crates.io-index"
956 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
957 | dependencies = [
958 | "getrandom",
959 | "libc",
960 | "rand_chacha",
961 | "rand_core",
962 | "rand_hc",
963 | ]
964 |
965 | [[package]]
966 | name = "rand_chacha"
967 | version = "0.2.2"
968 | source = "registry+https://github.com/rust-lang/crates.io-index"
969 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
970 | dependencies = [
971 | "ppv-lite86",
972 | "rand_core",
973 | ]
974 |
975 | [[package]]
976 | name = "rand_core"
977 | version = "0.5.1"
978 | source = "registry+https://github.com/rust-lang/crates.io-index"
979 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
980 | dependencies = [
981 | "getrandom",
982 | ]
983 |
984 | [[package]]
985 | name = "rand_hc"
986 | version = "0.2.0"
987 | source = "registry+https://github.com/rust-lang/crates.io-index"
988 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
989 | dependencies = [
990 | "rand_core",
991 | ]
992 |
993 | [[package]]
994 | name = "regex"
995 | version = "1.3.9"
996 | source = "registry+https://github.com/rust-lang/crates.io-index"
997 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
998 | dependencies = [
999 | "aho-corasick",
1000 | "memchr",
1001 | "regex-syntax",
1002 | "thread_local",
1003 | ]
1004 |
1005 | [[package]]
1006 | name = "regex-syntax"
1007 | version = "0.6.18"
1008 | source = "registry+https://github.com/rust-lang/crates.io-index"
1009 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
1010 |
1011 | [[package]]
1012 | name = "ron"
1013 | version = "0.3.0"
1014 | source = "registry+https://github.com/rust-lang/crates.io-index"
1015 | checksum = "a9fa11b7a38511d46ff1959ae46ebb60bd8a746f17bdd0206b4c8de7559ac47b"
1016 | dependencies = [
1017 | "base64 0.9.3",
1018 | "bitflags 1.2.1",
1019 | "serde",
1020 | ]
1021 |
1022 | [[package]]
1023 | name = "rustc-demangle"
1024 | version = "0.1.16"
1025 | source = "registry+https://github.com/rust-lang/crates.io-index"
1026 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
1027 |
1028 | [[package]]
1029 | name = "rustc_version"
1030 | version = "0.2.3"
1031 | source = "registry+https://github.com/rust-lang/crates.io-index"
1032 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
1033 | dependencies = [
1034 | "semver",
1035 | ]
1036 |
1037 | [[package]]
1038 | name = "ryu"
1039 | version = "1.0.5"
1040 | source = "registry+https://github.com/rust-lang/crates.io-index"
1041 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
1042 |
1043 | [[package]]
1044 | name = "safemem"
1045 | version = "0.3.3"
1046 | source = "registry+https://github.com/rust-lang/crates.io-index"
1047 | checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
1048 |
1049 | [[package]]
1050 | name = "scoped-tls"
1051 | version = "0.1.2"
1052 | source = "registry+https://github.com/rust-lang/crates.io-index"
1053 | checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
1054 |
1055 | [[package]]
1056 | name = "semver"
1057 | version = "0.9.0"
1058 | source = "registry+https://github.com/rust-lang/crates.io-index"
1059 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
1060 | dependencies = [
1061 | "semver-parser",
1062 | ]
1063 |
1064 | [[package]]
1065 | name = "semver-parser"
1066 | version = "0.7.0"
1067 | source = "registry+https://github.com/rust-lang/crates.io-index"
1068 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
1069 |
1070 | [[package]]
1071 | name = "serde"
1072 | version = "1.0.114"
1073 | source = "registry+https://github.com/rust-lang/crates.io-index"
1074 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
1075 | dependencies = [
1076 | "serde_derive",
1077 | ]
1078 |
1079 | [[package]]
1080 | name = "serde-xml-any"
1081 | version = "0.0.3"
1082 | source = "registry+https://github.com/rust-lang/crates.io-index"
1083 | checksum = "e281928a3e3104e809dbd19b78cef7ef7c29662cf2583a94c032485aa2e7586b"
1084 | dependencies = [
1085 | "error-chain",
1086 | "log 0.3.9",
1087 | "serde",
1088 | "xml-rs",
1089 | ]
1090 |
1091 | [[package]]
1092 | name = "serde_any"
1093 | version = "0.5.0"
1094 | source = "registry+https://github.com/rust-lang/crates.io-index"
1095 | checksum = "38cb506febacc2cf6533279947bd37b69ce91782af1aedf31c7e6181a77d46ee"
1096 | dependencies = [
1097 | "failure",
1098 | "ron",
1099 | "serde",
1100 | "serde-xml-any",
1101 | "serde_json",
1102 | "serde_urlencoded",
1103 | "serde_yaml",
1104 | "toml",
1105 | ]
1106 |
1107 | [[package]]
1108 | name = "serde_derive"
1109 | version = "1.0.114"
1110 | source = "registry+https://github.com/rust-lang/crates.io-index"
1111 | checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
1112 | dependencies = [
1113 | "proc-macro2",
1114 | "quote",
1115 | "syn",
1116 | ]
1117 |
1118 | [[package]]
1119 | name = "serde_json"
1120 | version = "1.0.56"
1121 | source = "registry+https://github.com/rust-lang/crates.io-index"
1122 | checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3"
1123 | dependencies = [
1124 | "itoa",
1125 | "ryu",
1126 | "serde",
1127 | ]
1128 |
1129 | [[package]]
1130 | name = "serde_urlencoded"
1131 | version = "0.5.5"
1132 | source = "registry+https://github.com/rust-lang/crates.io-index"
1133 | checksum = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a"
1134 | dependencies = [
1135 | "dtoa",
1136 | "itoa",
1137 | "serde",
1138 | "url 1.7.2",
1139 | ]
1140 |
1141 | [[package]]
1142 | name = "serde_yaml"
1143 | version = "0.7.5"
1144 | source = "registry+https://github.com/rust-lang/crates.io-index"
1145 | checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051"
1146 | dependencies = [
1147 | "dtoa",
1148 | "linked-hash-map",
1149 | "serde",
1150 | "yaml-rust",
1151 | ]
1152 |
1153 | [[package]]
1154 | name = "sha-1"
1155 | version = "0.8.2"
1156 | source = "registry+https://github.com/rust-lang/crates.io-index"
1157 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
1158 | dependencies = [
1159 | "block-buffer",
1160 | "digest",
1161 | "fake-simd",
1162 | "opaque-debug",
1163 | ]
1164 |
1165 | [[package]]
1166 | name = "slab"
1167 | version = "0.4.2"
1168 | source = "registry+https://github.com/rust-lang/crates.io-index"
1169 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1170 |
1171 | [[package]]
1172 | name = "strfmt"
1173 | version = "0.1.6"
1174 | source = "registry+https://github.com/rust-lang/crates.io-index"
1175 | checksum = "b278b244ef7aa5852b277f52dd0c6cac3a109919e1f6d699adde63251227a30f"
1176 |
1177 | [[package]]
1178 | name = "structopt"
1179 | version = "0.3.15"
1180 | source = "registry+https://github.com/rust-lang/crates.io-index"
1181 | checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c"
1182 | dependencies = [
1183 | "clap",
1184 | "lazy_static",
1185 | "structopt-derive",
1186 | ]
1187 |
1188 | [[package]]
1189 | name = "structopt-derive"
1190 | version = "0.4.8"
1191 | source = "registry+https://github.com/rust-lang/crates.io-index"
1192 | checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118"
1193 | dependencies = [
1194 | "heck",
1195 | "proc-macro-error",
1196 | "proc-macro2",
1197 | "quote",
1198 | "syn",
1199 | ]
1200 |
1201 | [[package]]
1202 | name = "syn"
1203 | version = "1.0.33"
1204 | source = "registry+https://github.com/rust-lang/crates.io-index"
1205 | checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
1206 | dependencies = [
1207 | "proc-macro2",
1208 | "quote",
1209 | "unicode-xid",
1210 | ]
1211 |
1212 | [[package]]
1213 | name = "syn-mid"
1214 | version = "0.5.0"
1215 | source = "registry+https://github.com/rust-lang/crates.io-index"
1216 | checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
1217 | dependencies = [
1218 | "proc-macro2",
1219 | "quote",
1220 | "syn",
1221 | ]
1222 |
1223 | [[package]]
1224 | name = "synstructure"
1225 | version = "0.12.4"
1226 | source = "registry+https://github.com/rust-lang/crates.io-index"
1227 | checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
1228 | dependencies = [
1229 | "proc-macro2",
1230 | "quote",
1231 | "syn",
1232 | "unicode-xid",
1233 | ]
1234 |
1235 | [[package]]
1236 | name = "termcolor"
1237 | version = "1.1.0"
1238 | source = "registry+https://github.com/rust-lang/crates.io-index"
1239 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
1240 | dependencies = [
1241 | "winapi-util",
1242 | ]
1243 |
1244 | [[package]]
1245 | name = "textwrap"
1246 | version = "0.11.0"
1247 | source = "registry+https://github.com/rust-lang/crates.io-index"
1248 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
1249 | dependencies = [
1250 | "unicode-width",
1251 | ]
1252 |
1253 | [[package]]
1254 | name = "thread_local"
1255 | version = "1.0.1"
1256 | source = "registry+https://github.com/rust-lang/crates.io-index"
1257 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
1258 | dependencies = [
1259 | "lazy_static",
1260 | ]
1261 |
1262 | [[package]]
1263 | name = "tinyvec"
1264 | version = "0.3.3"
1265 | source = "registry+https://github.com/rust-lang/crates.io-index"
1266 | checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed"
1267 |
1268 | [[package]]
1269 | name = "toml"
1270 | version = "0.4.10"
1271 | source = "registry+https://github.com/rust-lang/crates.io-index"
1272 | checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
1273 | dependencies = [
1274 | "serde",
1275 | ]
1276 |
1277 | [[package]]
1278 | name = "tungstenite"
1279 | version = "0.11.0"
1280 | source = "registry+https://github.com/rust-lang/crates.io-index"
1281 | checksum = "a5c7d464221cb0b538a1cd12f6d9127ed1e6bb7f3ffca98fb3cd4c6e3af8175c"
1282 | dependencies = [
1283 | "base64 0.12.3",
1284 | "byteorder",
1285 | "bytes",
1286 | "http",
1287 | "httparse",
1288 | "input_buffer",
1289 | "log 0.4.8",
1290 | "rand",
1291 | "sha-1",
1292 | "url 2.1.1",
1293 | "utf-8",
1294 | ]
1295 |
1296 | [[package]]
1297 | name = "typenum"
1298 | version = "1.12.0"
1299 | source = "registry+https://github.com/rust-lang/crates.io-index"
1300 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
1301 |
1302 | [[package]]
1303 | name = "unicode-bidi"
1304 | version = "0.3.4"
1305 | source = "registry+https://github.com/rust-lang/crates.io-index"
1306 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
1307 | dependencies = [
1308 | "matches",
1309 | ]
1310 |
1311 | [[package]]
1312 | name = "unicode-normalization"
1313 | version = "0.1.13"
1314 | source = "registry+https://github.com/rust-lang/crates.io-index"
1315 | checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
1316 | dependencies = [
1317 | "tinyvec",
1318 | ]
1319 |
1320 | [[package]]
1321 | name = "unicode-segmentation"
1322 | version = "1.6.0"
1323 | source = "registry+https://github.com/rust-lang/crates.io-index"
1324 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
1325 |
1326 | [[package]]
1327 | name = "unicode-width"
1328 | version = "0.1.8"
1329 | source = "registry+https://github.com/rust-lang/crates.io-index"
1330 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
1331 |
1332 | [[package]]
1333 | name = "unicode-xid"
1334 | version = "0.2.1"
1335 | source = "registry+https://github.com/rust-lang/crates.io-index"
1336 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1337 |
1338 | [[package]]
1339 | name = "url"
1340 | version = "1.7.2"
1341 | source = "registry+https://github.com/rust-lang/crates.io-index"
1342 | checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
1343 | dependencies = [
1344 | "idna 0.1.5",
1345 | "matches",
1346 | "percent-encoding 1.0.1",
1347 | ]
1348 |
1349 | [[package]]
1350 | name = "url"
1351 | version = "2.1.1"
1352 | source = "registry+https://github.com/rust-lang/crates.io-index"
1353 | checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
1354 | dependencies = [
1355 | "idna 0.2.0",
1356 | "matches",
1357 | "percent-encoding 2.1.0",
1358 | ]
1359 |
1360 | [[package]]
1361 | name = "utf-8"
1362 | version = "0.7.5"
1363 | source = "registry+https://github.com/rust-lang/crates.io-index"
1364 | checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
1365 |
1366 | [[package]]
1367 | name = "version_check"
1368 | version = "0.9.2"
1369 | source = "registry+https://github.com/rust-lang/crates.io-index"
1370 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1371 |
1372 | [[package]]
1373 | name = "wasi"
1374 | version = "0.9.0+wasi-snapshot-preview1"
1375 | source = "registry+https://github.com/rust-lang/crates.io-index"
1376 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1377 |
1378 | [[package]]
1379 | name = "winapi"
1380 | version = "0.3.9"
1381 | source = "registry+https://github.com/rust-lang/crates.io-index"
1382 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1383 | dependencies = [
1384 | "winapi-i686-pc-windows-gnu",
1385 | "winapi-x86_64-pc-windows-gnu",
1386 | ]
1387 |
1388 | [[package]]
1389 | name = "winapi-i686-pc-windows-gnu"
1390 | version = "0.4.0"
1391 | source = "registry+https://github.com/rust-lang/crates.io-index"
1392 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1393 |
1394 | [[package]]
1395 | name = "winapi-util"
1396 | version = "0.1.5"
1397 | source = "registry+https://github.com/rust-lang/crates.io-index"
1398 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1399 | dependencies = [
1400 | "winapi",
1401 | ]
1402 |
1403 | [[package]]
1404 | name = "winapi-x86_64-pc-windows-gnu"
1405 | version = "0.4.0"
1406 | source = "registry+https://github.com/rust-lang/crates.io-index"
1407 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1408 |
1409 | [[package]]
1410 | name = "xml-rs"
1411 | version = "0.6.1"
1412 | source = "registry+https://github.com/rust-lang/crates.io-index"
1413 | checksum = "e1945e12e16b951721d7976520b0832496ef79c31602c7a29d950de79ba74621"
1414 | dependencies = [
1415 | "bitflags 0.9.1",
1416 | ]
1417 |
1418 | [[package]]
1419 | name = "yaml-rust"
1420 | version = "0.4.4"
1421 | source = "registry+https://github.com/rust-lang/crates.io-index"
1422 | checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
1423 | dependencies = [
1424 | "linked-hash-map",
1425 | ]
1426 |
--------------------------------------------------------------------------------