├── .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 | --------------------------------------------------------------------------------