Initial
This commit is contained in:
commit
fbaf22ba7e
|
@ -0,0 +1 @@
|
|||
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
[workspace]
|
||||
members = ["lib-gst-meet", "gst-meet", "nice-gst-meet", "nice-gst-meet-sys"]
|
||||
|
||||
[patch.crates-io]
|
||||
minidom = { path = "../xmpp-rs/minidom" }
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,23 @@
|
|||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,118 @@
|
|||
# gst-meet: Integrate Jitsi Meet conferences with GStreamer pipelines
|
||||
|
||||
Note: gst-meet is in an **alpha** state and is under active development. The command-line options and the lib-gst-meet API are subject to change, and some important features (TURN, RTX, TCC, simulcast) are not yet fully functional.
|
||||
|
||||
gst-meet provides a library and tool for integrating Jitsi Meet conferences with GStreamer pipelines. You can pipe audio and video into a conference as a participant, and pipe out other participants' audio and video streams.
|
||||
|
||||
Thanks to GStreamer's flexibility and wide range of plugins, this enables many new possibilities.
|
||||
|
||||
## Installation
|
||||
|
||||
You will need the dependencies `glib`, `gstreamer` and `libnice`, as well as any GStreamer plugins you want to use in your pipelines, and a Rust toolchain ([rustup](https://rustup.rs/) is the easiest way to install one).
|
||||
|
||||
Then: `cargo install gst-meet`
|
||||
|
||||
To integrate gst-meet into your own application, look at `lib-gst-meet`.
|
||||
|
||||
## Pipeline Structure
|
||||
|
||||
You can pass two different pipeline fragments to gst-meet.
|
||||
|
||||
`--send-pipeline` is for sending audio and video. If it contains an element named `audio`, this audio will be streamed to the conference. The audio codec must be 48kHz Opus. If it contains an element named `video`, this video will be streamed to the conference. The video codec must match the codec passed to `--video-codec`, which is VP8 by default.
|
||||
|
||||
`--recv-pipeline-participant-template` is for receiving audio and video from other participants. This pipeline will be created once for each other participant in the conference. If it contains an element named `audio`, the participant's audio (48kHz Opus) will be sent to that element. If it contains an element named `video`, the participant's video (encoded with the codec selected by `--video-codec`) will be sent to that element. The strings `{jid}`, `{jid_user}`, `{participant_id}` and `{nick}` are replaced in the template with the participant's full JID, user part, MUC JID resource part (a.k.a. participant/occupant ID) and nickname respectively.
|
||||
|
||||
## Examples
|
||||
|
||||
A few examples of `gst-meet` usage are below. The GStreamer reference provides full details on available pipeline elements.
|
||||
|
||||
`gst-meet --help` lists full usage information.
|
||||
|
||||
Stream an Opus audio file to the conference. This is very efficient; the Opus data in the file is streamed directly without transcoding:
|
||||
|
||||
```
|
||||
gst-meet --web-socket-url=wss://your.jitsi.domain/xmpp-websocket \
|
||||
--xmpp-domain=your.jitsi.domain \
|
||||
--room-name=roomname \
|
||||
--send-pipeline="filesrc location=sample.opus ! queue ! oggdemux name=audio"
|
||||
```
|
||||
|
||||
Stream a FLAC audio file to the conference, transcoding it to Opus:
|
||||
|
||||
```
|
||||
gst-meet --web-socket-url=wss://your.jitsi.domain/xmpp-websocket \
|
||||
--xmpp-domain=your.jitsi.domain \
|
||||
--room-name=roomname \
|
||||
--send-pipeline="filesrc location=shake-it-off.flac ! queue ! flacdec ! audioconvert ! audioresample ! opusenc name=audio"
|
||||
```
|
||||
|
||||
Stream a .webm file containing VP8 video and Vorbis audio to the conference. This pipeline passes the VP8 stream through efficiently without transcoding, and transcodes the audio from Vorbis to Opus:
|
||||
|
||||
```
|
||||
gst-meet --web-socket-url=wss://your.jitsi.domain/xmpp-websocket \
|
||||
--xmpp-domain=your.jitsi.domain \
|
||||
--room-name=roomname \
|
||||
--send-pipeline="filesrc location=big-buck-bunny_trailer.webm ! queue ! matroskademux name=demuxer
|
||||
demuxer.video_0 ! queue name=video
|
||||
demuxer.audio_0 ! queue ! vorbisdec ! audioconvert ! audioresample ! opusenc name=audio"
|
||||
```
|
||||
|
||||
Stream the default macOS video & audio inputs to the conference, encoding as VP8 and Opus, display incoming video streams and play back incoming audio (a very basic, but completely native, Jitsi Meet conference!):
|
||||
|
||||
```
|
||||
gst-meet --web-socket-url=wss://your.jitsi.domain/xmpp-websocket \
|
||||
--xmpp-domain=your.jitsi.domain \
|
||||
--room-name=roomname \
|
||||
--send-pipeline="avfvideosrc ! queue ! videoconvert ! vp8enc buffer-size=1000 deadline=1 name=video
|
||||
osxaudiosrc ! queue ! audioconvert ! audioresample ! opusenc name=audio" \
|
||||
--recv-pipeline-participant-template="opusdec name=audio ! autoaudiosink
|
||||
vp8dec name=video ! videoconvert ! autovideosink"
|
||||
```
|
||||
|
||||
Record a .webm file for each other participant, containing VP8 video and Opus audio, without needing to do any transcoding:
|
||||
|
||||
```
|
||||
gst-meet --web-socket-url=wss://your.jitsi.domain/xmpp-websocket \
|
||||
--xmpp-domain=your.jitsi.domain \
|
||||
--room-name=roomname \
|
||||
--recv-pipeline-participant-template="webmmux name=muxer ! filesink location={participant_id}.webm
|
||||
opusparse name=audio ! muxer.audio_0
|
||||
capsfilter caps=video/x-vp8 name=video ! muxer.video_0"
|
||||
```
|
||||
|
||||
Play a YouTube video in the conference. By requesting Opus audio and VP9 video from YouTube, and setting the Jitsi Meet video codec to VP9, no transcoding is necessary:
|
||||
|
||||
```
|
||||
YOUTUBE_URL="https://www.youtube.com/watch?v=vjV_2Ri2rfE"
|
||||
gst-meet --web-socket-url=wss://your.jitsi.domain/xmpp-websocket \
|
||||
--xmpp-domain=your.jitsi.domain \
|
||||
--room-name=roomname \
|
||||
--video-codec=vp9 \
|
||||
--send-pipeline="curlhttpsrc location=\"$(youtube-dl -g $YOUTUBE_URL -f 'bestaudio[acodec=opus]')\" ! queue ! matroskademux name=audiodemux
|
||||
curlhttpsrc location=\"$(youtube-dl -g $YOUTUBE_URL -f 'bestvideo[vcodec=vp9]')\" ! queue ! matroskademux name=videodemux
|
||||
audiodemux.audio_0 ! queue ! clocksync name=audio
|
||||
videodemux.video_0 ! queue ! clocksync name=video"
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
It can sometimes be tricky to get GStreamer pipeline syntax and structure correct. To help with this, you can try setting the `GST_DEBUG` environment variable (for example, `3` is modestly verbose, while `6` produces copious per-packet output). You can also set `GST_DEBUG_DUMP_DOT_DIR` to the relative path to a directory (which must already exist). `.dot` files containing the pipeline graph will be saved to this directory, and can be converted to `.png` with the `dot` tool from GraphViz; for example `dot filename.dot -Tpng > filename.png`.
|
||||
|
||||
## License
|
||||
|
||||
`gst-meet`, `lib-gst-meet`, `nice` and `nice-sys` are licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
The dependency `xmpp-parsers` is licensed under the Mozilla Public License, Version 2.0, https://www.mozilla.org/en-US/MPL/2.0/
|
||||
|
||||
The dependency `gstreamer` is licensed under the GNU Lesser General Public License, Version 2.1, https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html.
|
||||
|
||||
## Contribution
|
||||
|
||||
Any kinds of contributions are welcome as a pull request.
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in these crates by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
|
|
@ -0,0 +1,44 @@
|
|||
[advisories]
|
||||
db-path = "~/.cargo/advisory-db"
|
||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
vulnerability = "deny"
|
||||
unmaintained = "deny"
|
||||
yanked = "deny"
|
||||
notice = "deny"
|
||||
ignore = []
|
||||
|
||||
[licenses]
|
||||
unlicensed = "deny"
|
||||
allow = ["MPL-2.0"]
|
||||
deny = []
|
||||
copyleft = "deny"
|
||||
allow-osi-fsf-free = "either"
|
||||
default = "deny"
|
||||
confidence-threshold = 1.0
|
||||
exceptions = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "ring"
|
||||
version = "*"
|
||||
expression = "MIT AND ISC AND OpenSSL"
|
||||
license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "webpki"
|
||||
version = "*"
|
||||
expression = "ISC"
|
||||
license-files = [{ path = "LICENSE", hash = 0x001c7e6c }]
|
||||
|
||||
[bans]
|
||||
multiple-versions = "deny"
|
||||
wildcards = "deny"
|
||||
highlight = "all"
|
||||
allow = []
|
||||
deny = []
|
||||
skip = []
|
||||
skip-tree = []
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "gst-meet"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
license = "MIT/Apache-2.0"
|
||||
authors = ["Jasper Hugo <jasper@avstack.io>"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1", default-features = false, features = ["std"] }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
glib = { version = "0.14", default-features = false, features = ["log"] }
|
||||
gstreamer = { version = "0.17", default-features = false, features = ["v1_18"] }
|
||||
lib-gst-meet = { path = "../lib-gst-meet" }
|
||||
structopt = { version = "0.3", default-features = false }
|
||||
tokio = { version = "1", default-features = false, features = ["macros", "rt-multi-thread", "signal", "sync", "time"] }
|
||||
tokio-stream = { version = "0.1", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, features = ["attributes", "std"] }
|
||||
tracing-subscriber = { version = "0.2", default-features = false, features = [
|
||||
"chrono",
|
||||
"env-filter",
|
||||
"fmt",
|
||||
"smallvec",
|
||||
"parking_lot",
|
||||
"tracing-log",
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa = { version = "0.24", default-features = false }
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,23 @@
|
|||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,224 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
#[cfg(target_os = "macos")]
|
||||
use cocoa::appkit::NSApplication;
|
||||
use gstreamer::{
|
||||
prelude::{ElementExt, GstBinExt},
|
||||
GhostPad,
|
||||
};
|
||||
use lib_gst_meet::{JitsiConferenceConfig, JitsiConnection};
|
||||
use structopt::StructOpt;
|
||||
use tokio::{signal::ctrl_c, task, time::timeout};
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[derive(Debug, Clone, StructOpt)]
|
||||
#[structopt(
|
||||
name = "gst-meet",
|
||||
about = "Connect a GStreamer pipeline to a Jitsi Meet conference."
|
||||
)]
|
||||
struct Opt {
|
||||
#[structopt(long)]
|
||||
web_socket_url: String,
|
||||
#[structopt(long)]
|
||||
xmpp_domain: String,
|
||||
#[structopt(long)]
|
||||
room_name: String,
|
||||
#[structopt(long)]
|
||||
muc_domain: Option<String>,
|
||||
#[structopt(long)]
|
||||
focus_jid: Option<String>,
|
||||
#[structopt(long, default_value = "vp8")]
|
||||
video_codec: String,
|
||||
#[structopt(long, default_value = "gst-meet")]
|
||||
nick: String,
|
||||
#[structopt(long, default_value)]
|
||||
region: String,
|
||||
#[structopt(long)]
|
||||
send_pipeline: Option<String>,
|
||||
#[structopt(long)]
|
||||
recv_pipeline_participant_template: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
main_inner().await
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main() {
|
||||
// GStreamer requires an NSApp event loop in order for osxvideosink etc to work.
|
||||
let app = unsafe { cocoa::appkit::NSApp() };
|
||||
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.spawn(async move {
|
||||
main_inner().await.unwrap();
|
||||
unsafe {
|
||||
cocoa::appkit::NSApp().stop_(cocoa::base::nil);
|
||||
}
|
||||
std::process::exit(0);
|
||||
});
|
||||
|
||||
unsafe {
|
||||
app.run();
|
||||
}
|
||||
}
|
||||
|
||||
async fn main_inner() -> Result<()> {
|
||||
init_tracing();
|
||||
|
||||
let opt = Opt::from_args();
|
||||
|
||||
glib::log_set_default_handler(glib::rust_log_handler);
|
||||
let main_loop = glib::MainLoop::new(None, false);
|
||||
gstreamer::init().unwrap();
|
||||
|
||||
let parsed_bin = opt
|
||||
.send_pipeline
|
||||
.as_ref()
|
||||
.map(|pipeline| gstreamer::parse_bin_from_description(pipeline, false))
|
||||
.transpose()?;
|
||||
|
||||
let (connection, background) =
|
||||
JitsiConnection::new(&opt.web_socket_url, &opt.xmpp_domain).await?;
|
||||
|
||||
tokio::spawn(background);
|
||||
|
||||
connection.connect().await?;
|
||||
|
||||
let room_jid = format!(
|
||||
"{}@{}",
|
||||
opt.room_name,
|
||||
opt
|
||||
.muc_domain
|
||||
.clone()
|
||||
.unwrap_or_else(|| { format!("conference.{}", opt.xmpp_domain) }),
|
||||
);
|
||||
|
||||
let focus_jid = opt
|
||||
.focus_jid
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("focus@auth.{}/focus", opt.xmpp_domain,));
|
||||
|
||||
let Opt {
|
||||
nick,
|
||||
region,
|
||||
video_codec,
|
||||
recv_pipeline_participant_template,
|
||||
..
|
||||
} = opt;
|
||||
|
||||
let config = JitsiConferenceConfig {
|
||||
muc: room_jid.parse()?,
|
||||
focus: focus_jid.parse()?,
|
||||
nick,
|
||||
region,
|
||||
video_codec,
|
||||
};
|
||||
|
||||
let conference = connection
|
||||
.join_conference(main_loop.context(), config)
|
||||
.await?;
|
||||
|
||||
if let Some(bin) = parsed_bin {
|
||||
conference.add_bin(&bin).await?;
|
||||
|
||||
if let Some(audio) = bin.by_name("audio") {
|
||||
info!("Found audio element in pipeline, linking...");
|
||||
let audio_sink = conference.audio_sink_element().await?;
|
||||
audio.link(&audio_sink)?;
|
||||
}
|
||||
|
||||
if let Some(video) = bin.by_name("video") {
|
||||
info!("Found video element in pipeline, linking...");
|
||||
let video_sink = conference.video_sink_element().await?;
|
||||
video.link(&video_sink)?;
|
||||
}
|
||||
}
|
||||
|
||||
conference
|
||||
.on_participant(move |participant| {
|
||||
let recv_pipeline_participant_template = recv_pipeline_participant_template.clone();
|
||||
Box::pin(async move {
|
||||
info!("New participant: {:?}", participant);
|
||||
|
||||
if let Some(template) = recv_pipeline_participant_template {
|
||||
let pipeline_description = template
|
||||
.replace("{jid}", &participant.jid.to_string())
|
||||
.replace(
|
||||
"{jid_user}",
|
||||
&participant.jid.node.context("jid missing node")?,
|
||||
)
|
||||
.replace("{participant_id}", &participant.muc_jid.resource)
|
||||
.replace("{nick}", &participant.nick.unwrap_or_default());
|
||||
|
||||
let bin = gstreamer::parse_bin_from_description(&pipeline_description, false)
|
||||
.context("failed to parse recv pipeline participant template")?;
|
||||
|
||||
if let Some(audio_sink_element) = bin.by_name("audio") {
|
||||
let sink_pad = audio_sink_element.static_pad("sink").context(
|
||||
"audio sink element in recv pipeline participant template has no sink pad",
|
||||
)?;
|
||||
bin.add_pad(&GhostPad::with_target(Some("audio"), &sink_pad)?)?;
|
||||
}
|
||||
else {
|
||||
info!("No audio sink element found in recv pipeline participant template");
|
||||
}
|
||||
|
||||
if let Some(video_sink_element) = bin.by_name("video") {
|
||||
let sink_pad = video_sink_element.static_pad("sink").context(
|
||||
"video sink element in recv pipeline participant template has no sink pad",
|
||||
)?;
|
||||
bin.add_pad(&GhostPad::with_target(Some("video"), &sink_pad)?)?;
|
||||
}
|
||||
else {
|
||||
info!("No video sink element found in recv pipeline participant template");
|
||||
}
|
||||
|
||||
Ok(Some(bin))
|
||||
}
|
||||
else {
|
||||
info!("No template for handling new participant");
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
conference
|
||||
.set_pipeline_state(gstreamer::State::Playing)
|
||||
.await?;
|
||||
|
||||
let conference_ = conference.clone();
|
||||
let main_loop_ = main_loop.clone();
|
||||
tokio::spawn(async move {
|
||||
ctrl_c().await.unwrap();
|
||||
|
||||
info!("Exiting...");
|
||||
|
||||
match timeout(Duration::from_secs(10), conference_.leave()).await {
|
||||
Ok(Ok(_)) => {},
|
||||
Ok(Err(e)) => warn!("Error leaving conference: {:?}", e),
|
||||
Err(_) => warn!("Timed out leaving conference"),
|
||||
}
|
||||
|
||||
main_loop_.quit();
|
||||
});
|
||||
|
||||
task::spawn_blocking(move || main_loop.run()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_tracing() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE)
|
||||
.with_target(false)
|
||||
.init();
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
[package]
|
||||
name = "lib-gst-meet"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
license = "MIT/Apache-2.0"
|
||||
authors = ["Jasper Hugo <jasper@avstack.io>"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1", default-features = false, features = ["std"] }
|
||||
async-stream = { version = "0.3", default-features = false }
|
||||
async-trait = { version = "0.1", default-features = false }
|
||||
bytes = { version = "1", default-features = false, features = ["std"] }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
glib = { version = "0.14", default-features = false }
|
||||
gstreamer = { version = "0.17", default-features = false, features = ["v1_18"] }
|
||||
hex = { version = "0.4", default-features = false, features = ["std"] }
|
||||
itertools = { version = "0.10", default-features = false, features = ["use_std"] }
|
||||
libc = { version = "0.2", default-features = false }
|
||||
maplit = { version = "1", default-features = false }
|
||||
nice-gst-meet = { path = "../nice-gst-meet", default-features = false, features = ["v0_1_18"] }
|
||||
once_cell = { version = "1", default-features = false, features = ["std"] }
|
||||
pem = { version = "0.8", default-features = false }
|
||||
rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] }
|
||||
rcgen = { version = "0.8", default-features = false }
|
||||
ring = { version = "0.16", default-features = false }
|
||||
tokio = { version = "1", default-features = false, features = ["sync", "time"] }
|
||||
tokio-stream = { version = "0.1", default-features = false, features = ["time"] }
|
||||
tokio-tungstenite = { version = "0.14", default-features = false, features = ["connect", "rustls-tls"] }
|
||||
tracing = { version = "0.1", default-features = false, features = ["attributes", "std"] }
|
||||
uuid = { version = "0.8", default-features = false, features = ["v4"] }
|
||||
xmpp-parsers = { path = "../../xmpp-rs/xmpp-parsers", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
pkg-config = { version = "0.3", default-features = false }
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,23 @@
|
|||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,579 @@
|
|||
use std::{collections::HashMap, convert::TryFrom, fmt, future::Future, pin::Pin, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::stream::StreamExt;
|
||||
use glib::ObjectExt;
|
||||
use gstreamer::prelude::{ElementExt, ElementExtManual, GstBinExt};
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::sync::{mpsc, oneshot, Mutex};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
use xmpp_parsers::{
|
||||
disco::{DiscoInfoQuery, DiscoInfoResult, Feature},
|
||||
ecaps2::{self, ECaps2},
|
||||
hashes::Algo,
|
||||
iq::{Iq, IqType},
|
||||
jingle::{Action, Jingle},
|
||||
muc::{Muc, MucUser},
|
||||
ns,
|
||||
presence::{self, Presence},
|
||||
BareJid, Element, FullJid, Jid,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
jingle::JingleSession,
|
||||
source::MediaType,
|
||||
stanza_filter::StanzaFilter,
|
||||
xmpp,
|
||||
};
|
||||
|
||||
static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| DiscoInfoResult {
|
||||
node: None,
|
||||
identities: vec![],
|
||||
features: vec![
|
||||
Feature::new(ns::JINGLE_RTP_AUDIO),
|
||||
Feature::new(ns::JINGLE_RTP_VIDEO),
|
||||
Feature::new(ns::JINGLE_ICE_UDP),
|
||||
Feature::new(ns::JINGLE_DTLS),
|
||||
|
||||
// not supported yet: rtx
|
||||
// Feature::new("urn:ietf:rfc:4588"),
|
||||
|
||||
// not supported yet: rtcp remb
|
||||
// Feature::new("http://jitsi.org/remb"),
|
||||
|
||||
// not supported yet: transport-cc
|
||||
// Feature::new("http://jitsi.org/tcc"),
|
||||
|
||||
// rtcp-mux
|
||||
Feature::new("urn:ietf:rfc:5761"),
|
||||
// rtp-bundle
|
||||
Feature::new("urn:ietf:rfc:5888"),
|
||||
// opus red
|
||||
Feature::new("http://jitsi.org/opus-red"),
|
||||
],
|
||||
extensions: vec![],
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum JitsiConferenceState {
|
||||
Discovering,
|
||||
JoiningMuc,
|
||||
Idle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JitsiConferenceConfig {
|
||||
pub muc: BareJid,
|
||||
pub focus: FullJid,
|
||||
pub nick: String,
|
||||
pub region: String,
|
||||
pub video_codec: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JitsiConference {
|
||||
pub(crate) glib_main_context: glib::MainContext,
|
||||
pub(crate) jid: FullJid,
|
||||
pub(crate) xmpp_tx: mpsc::Sender<Element>,
|
||||
pub(crate) config: JitsiConferenceConfig,
|
||||
pub(crate) external_services: Vec<xmpp::extdisco::Service>,
|
||||
pub(crate) inner: Arc<Mutex<JitsiConferenceInner>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for JitsiConference {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("JitsiConference")
|
||||
.field("jid", &self.jid)
|
||||
.field("config", &self.config)
|
||||
.field("inner", &self.inner)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Participant {
|
||||
pub jid: FullJid,
|
||||
pub muc_jid: FullJid,
|
||||
pub nick: Option<String>,
|
||||
bin: Option<gstreamer::Bin>,
|
||||
}
|
||||
|
||||
pub(crate) struct JitsiConferenceInner {
|
||||
pub(crate) jingle_session: Option<JingleSession>,
|
||||
participants: HashMap<String, Participant>,
|
||||
on_participant: Option<
|
||||
Arc<
|
||||
dyn (Fn(Participant) -> Pin<Box<dyn Future<Output = Result<Option<gstreamer::Bin>>> + Send>>)
|
||||
+ Send
|
||||
+ Sync,
|
||||
>,
|
||||
>,
|
||||
state: JitsiConferenceState,
|
||||
connected_tx: Option<oneshot::Sender<()>>,
|
||||
connected_rx: Option<oneshot::Receiver<()>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for JitsiConferenceInner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("JitsiConferenceInner")
|
||||
.field("state", &self.state)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl JitsiConference {
|
||||
#[tracing::instrument(level = "debug", skip(xmpp_tx), err)]
|
||||
pub(crate) async fn new(
|
||||
glib_main_context: glib::MainContext,
|
||||
jid: FullJid,
|
||||
xmpp_tx: mpsc::Sender<Element>,
|
||||
config: JitsiConferenceConfig,
|
||||
external_services: Vec<xmpp::extdisco::Service>,
|
||||
) -> Result<Self> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
Ok(Self {
|
||||
glib_main_context,
|
||||
jid,
|
||||
xmpp_tx,
|
||||
config,
|
||||
external_services,
|
||||
inner: Arc::new(Mutex::new(JitsiConferenceInner {
|
||||
state: JitsiConferenceState::Discovering,
|
||||
participants: HashMap::new(),
|
||||
on_participant: None,
|
||||
jingle_session: None,
|
||||
connected_tx: Some(tx),
|
||||
connected_rx: Some(rx),
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn connected(&self) -> Result<()> {
|
||||
let rx = {
|
||||
let mut locked_inner = self.inner.lock().await;
|
||||
locked_inner
|
||||
.connected_rx
|
||||
.take()
|
||||
.context("connected() called twice")?
|
||||
};
|
||||
rx.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", err)]
|
||||
pub async fn leave(self) -> Result<()> {
|
||||
let mut inner = self.inner.lock().await;
|
||||
|
||||
if let Some(jingle_session) = inner.jingle_session.take() {
|
||||
debug!("pausing all sinks");
|
||||
jingle_session.pause_all_sinks();
|
||||
|
||||
debug!("setting pipeline state to NULL");
|
||||
if let Err(e) = jingle_session.pipeline().set_state(gstreamer::State::Null) {
|
||||
warn!("failed to set pipeline state to NULL: {:?}", e);
|
||||
}
|
||||
|
||||
debug!("waiting for state change to complete");
|
||||
let _ = jingle_session.pipeline_stopped().await;
|
||||
}
|
||||
|
||||
// should leave the XMPP muc gracefully, instead of just disconnecting
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jid_in_muc(&self) -> Result<FullJid> {
|
||||
let resource = self
|
||||
.jid
|
||||
.node
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("invalid jid"))?
|
||||
.split('-')
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("invalid jid"))?;
|
||||
Ok(self.config.muc.clone().with_resource(resource))
|
||||
}
|
||||
|
||||
pub(crate) fn focus_jid_in_muc(&self) -> Result<FullJid> {
|
||||
Ok(self.config.muc.clone().with_resource("focus"))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", err)]
|
||||
async fn send_presence(&self, payloads: Vec<Element>) -> Result<()> {
|
||||
let mut presence = Presence::new(presence::Type::None).with_to(self.jid_in_muc()?);
|
||||
presence.payloads = payloads;
|
||||
self.xmpp_tx.send(presence.into()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", err)]
|
||||
pub async fn set_muted(&self, media_type: MediaType, muted: bool) -> Result<()> {
|
||||
self
|
||||
.send_presence(vec![
|
||||
Element::builder(media_type.jitsi_muted_presence_element_name(), "")
|
||||
.append(muted.to_string())
|
||||
.build(),
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn pipeline(&self) -> Result<gstreamer::Pipeline> {
|
||||
Ok(
|
||||
self
|
||||
.inner
|
||||
.lock()
|
||||
.await
|
||||
.jingle_session
|
||||
.as_ref()
|
||||
.context("not connected (no jingle session)")?
|
||||
.pipeline(),
|
||||
)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", err)]
|
||||
pub async fn add_bin(&self, bin: &gstreamer::Bin) -> Result<()> {
|
||||
let pipeline = self.pipeline().await?;
|
||||
pipeline.add(bin)?;
|
||||
bin.sync_state_with_parent()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", err)]
|
||||
pub async fn set_pipeline_state(&self, state: gstreamer::State) -> Result<()> {
|
||||
self.pipeline().await?.call_async(move |p| {
|
||||
if let Err(e) = p.set_state(state) {
|
||||
error!("pipeline set_state: {:?}", e);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn audio_sink_element(&self) -> Result<gstreamer::Element> {
|
||||
Ok(
|
||||
self
|
||||
.inner
|
||||
.lock()
|
||||
.await
|
||||
.jingle_session
|
||||
.as_ref()
|
||||
.context("not connected (no jingle session)")?
|
||||
.audio_sink_element(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn video_sink_element(&self) -> Result<gstreamer::Element> {
|
||||
Ok(
|
||||
self
|
||||
.inner
|
||||
.lock()
|
||||
.await
|
||||
.jingle_session
|
||||
.as_ref()
|
||||
.context("not connected (no jingle session)")?
|
||||
.video_sink_element(),
|
||||
)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(f))]
|
||||
pub async fn on_participant(
|
||||
&self,
|
||||
f: impl (Fn(Participant) -> Pin<Box<dyn Future<Output = Result<Option<gstreamer::Bin>>> + Send>>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
let f = Arc::new(f);
|
||||
let f2 = f.clone();
|
||||
let existing_participants: Vec<_> = {
|
||||
let mut locked_inner = self.inner.lock().await;
|
||||
locked_inner.on_participant = Some(f2);
|
||||
locked_inner.participants.values().cloned().collect()
|
||||
};
|
||||
for participant in existing_participants {
|
||||
debug!(
|
||||
"calling on_participant with existing participant: {:?}",
|
||||
participant
|
||||
);
|
||||
match f(participant.clone()).await {
|
||||
Ok(Some(bin)) => {
|
||||
bin
|
||||
.set_property(
|
||||
"name",
|
||||
format!("participant_{}", participant.muc_jid.resource),
|
||||
)
|
||||
.unwrap();
|
||||
match self.add_bin(&bin).await {
|
||||
Ok(_) => {
|
||||
let mut locked_inner = self.inner.lock().await;
|
||||
if let Some(p) = locked_inner
|
||||
.participants
|
||||
.get_mut(&participant.muc_jid.resource)
|
||||
{
|
||||
p.bin = Some(bin);
|
||||
}
|
||||
},
|
||||
Err(e) => warn!("failed to add participant bin: {:?}", e),
|
||||
}
|
||||
},
|
||||
Ok(None) => {},
|
||||
Err(e) => warn!("on_participant failed: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StanzaFilter for JitsiConference {
|
||||
#[tracing::instrument(level = "trace")]
|
||||
fn filter(&self, element: &Element) -> bool {
|
||||
element.attr("from") == Some(self.config.focus.to_string().as_str())
|
||||
&& element.is("iq", "jabber:client")
|
||||
|| element
|
||||
.attr("from")
|
||||
.and_then(|from| from.parse::<BareJid>().ok())
|
||||
.map(|jid| jid == self.config.muc)
|
||||
.unwrap_or_default()
|
||||
&& (element.is("presence", "jabber:client") || element.is("iq", "jabber:client"))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", err)]
|
||||
async fn take(&self, element: Element) -> Result<()> {
|
||||
let mut locked_inner = self.inner.lock().await;
|
||||
|
||||
use JitsiConferenceState::*;
|
||||
match locked_inner.state {
|
||||
Discovering => {
|
||||
let iq = Iq::try_from(element)?;
|
||||
if let IqType::Result(Some(element)) = iq.payload {
|
||||
let ready: bool = element
|
||||
.attr("ready")
|
||||
.ok_or_else(|| anyhow!("missing ready attribute on conference IQ"))?
|
||||
.parse()?;
|
||||
if !ready {
|
||||
bail!("focus reports room not ready");
|
||||
}
|
||||
}
|
||||
else {
|
||||
bail!("focus IQ failed");
|
||||
};
|
||||
|
||||
let jitsi_disco_info = DiscoInfoResult {
|
||||
node: Some("http://jitsi.org/jitsimeet".to_string()),
|
||||
identities: vec![],
|
||||
features: vec![],
|
||||
extensions: vec![],
|
||||
};
|
||||
|
||||
let jitsi_disco_hash =
|
||||
ecaps2::hash_ecaps2(&ecaps2::compute_disco(&jitsi_disco_info)?, Algo::Sha_256)?;
|
||||
self
|
||||
.send_presence(vec![
|
||||
Muc::new().into(),
|
||||
ECaps2::new(vec![jitsi_disco_hash]).into(),
|
||||
Element::builder("stats-id", "").append("gst-meet").build(),
|
||||
Element::builder("jitsi_participant_codecType", "")
|
||||
.append(self.config.video_codec.as_str())
|
||||
.build(),
|
||||
Element::builder("jitsi_participant_region", "")
|
||||
.append(self.config.region.as_str())
|
||||
.build(),
|
||||
Element::builder("audiomuted", "").append("false").build(),
|
||||
Element::builder("videomuted", "").append("false").build(),
|
||||
Element::builder("nick", "http://jabber.org/protocol/nick")
|
||||
.append(self.config.nick.as_str())
|
||||
.build(),
|
||||
Element::builder("region", "http://jitsi.org/jitsi-meet")
|
||||
.attr("id", &self.config.region)
|
||||
.build(),
|
||||
])
|
||||
.await?;
|
||||
locked_inner.state = JoiningMuc;
|
||||
},
|
||||
JoiningMuc => {
|
||||
let presence = Presence::try_from(element)?;
|
||||
if BareJid::from(presence.from.as_ref().unwrap().clone()) == self.config.muc {
|
||||
debug!("Joined MUC: {}", self.config.muc);
|
||||
locked_inner.state = Idle;
|
||||
}
|
||||
},
|
||||
Idle => {
|
||||
if let Ok(iq) = Iq::try_from(element.clone()) {
|
||||
match iq.payload {
|
||||
IqType::Get(element) => {
|
||||
if let Ok(query) = DiscoInfoQuery::try_from(element) {
|
||||
debug!(
|
||||
"Received disco info query from {} for node {:?}",
|
||||
iq.from.as_ref().unwrap(),
|
||||
query.node
|
||||
);
|
||||
if query.node.is_none() {
|
||||
let iq = Iq::from_result(iq.id, Some(DISCO_INFO.clone()))
|
||||
.with_from(Jid::Full(self.jid.clone()))
|
||||
.with_to(iq.from.unwrap());
|
||||
self.xmpp_tx.send(iq.into()).await?;
|
||||
}
|
||||
else {
|
||||
panic!("don't know how to handle disco info node: {:?}", query.node);
|
||||
}
|
||||
}
|
||||
},
|
||||
IqType::Set(element) => {
|
||||
if let Ok(jingle) = Jingle::try_from(element) {
|
||||
if let Some(Jid::Full(from_jid)) = iq.from {
|
||||
if jingle.action == Action::SessionInitiate {
|
||||
if from_jid.resource == "focus" {
|
||||
// Acknowledge the IQ
|
||||
let result_iq = Iq::empty_result(Jid::Full(from_jid.clone()), iq.id.clone())
|
||||
.with_from(Jid::Full(self.jid.clone()));
|
||||
self.xmpp_tx.send(result_iq.into()).await?;
|
||||
|
||||
locked_inner.jingle_session =
|
||||
Some(JingleSession::initiate(self, jingle).await?);
|
||||
}
|
||||
else {
|
||||
debug!("Ignored Jingle session-initiate from {}", from_jid);
|
||||
}
|
||||
}
|
||||
else if jingle.action == Action::SourceAdd {
|
||||
debug!("Received Jingle source-add");
|
||||
|
||||
// Acknowledge the IQ
|
||||
let result_iq = Iq::empty_result(Jid::Full(from_jid.clone()), iq.id.clone())
|
||||
.with_from(Jid::Full(self.jid.clone()));
|
||||
self.xmpp_tx.send(result_iq.into()).await?;
|
||||
|
||||
locked_inner
|
||||
.jingle_session
|
||||
.as_mut()
|
||||
.context("not connected (no jingle session")?
|
||||
.source_add(jingle)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug!("Received Jingle IQ from invalid JID: {:?}", iq.from);
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug!("Received non-Jingle IQ");
|
||||
}
|
||||
},
|
||||
IqType::Result(_) => {
|
||||
if let Some(jingle_session) = locked_inner.jingle_session.as_ref() {
|
||||
if Some(iq.id) == jingle_session.accept_iq_id {
|
||||
let colibri_url = jingle_session.colibri_url.clone();
|
||||
|
||||
locked_inner.jingle_session.as_mut().unwrap().accept_iq_id = None;
|
||||
|
||||
debug!("Focus acknowledged session-accept");
|
||||
|
||||
if let Some(colibri_url) = colibri_url {
|
||||
info!("Connecting Colibri WebSocket to {}", colibri_url);
|
||||
|
||||
let request =
|
||||
tokio_tungstenite::tungstenite::http::Request::get(colibri_url).body(())?;
|
||||
let (colibri_websocket, _response) =
|
||||
tokio_tungstenite::connect_async(request).await?;
|
||||
info!("Connected Colibri WebSocket");
|
||||
|
||||
let (colibri_sink, mut colibri_stream) = colibri_websocket.split();
|
||||
let colibri_receive_task = tokio::spawn(async move {
|
||||
while let Some(msg) = colibri_stream.next().await {
|
||||
debug!("colibri: {:?}", msg);
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
});
|
||||
let (colibri_tx, colibri_rx) = mpsc::channel(8);
|
||||
locked_inner.jingle_session.as_mut().unwrap().colibri_tx = Some(colibri_tx);
|
||||
let colibri_transmit_task = tokio::spawn(async move {
|
||||
let stream = ReceiverStream::new(colibri_rx);
|
||||
stream.forward(colibri_sink).await?;
|
||||
Ok::<_, anyhow::Error>(())
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::select! {
|
||||
res = colibri_receive_task => if let Ok(Err(e)) = res {
|
||||
error!("colibri read loop: {:?}", e);
|
||||
},
|
||||
res = colibri_transmit_task => if let Ok(Err(e)) = res {
|
||||
error!("colibri write loop: {:?}", e);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(connected_tx) = locked_inner.connected_tx.take() {
|
||||
connected_tx.send(()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
else if let Ok(presence) = Presence::try_from(element) {
|
||||
if let Jid::Full(from) = presence
|
||||
.from
|
||||
.as_ref()
|
||||
.context("missing from in presence")?
|
||||
.clone()
|
||||
{
|
||||
let bare_from: BareJid = from.clone().into();
|
||||
if bare_from == self.config.muc && from.resource != "focus" {
|
||||
trace!("received MUC presence from {}", from.resource);
|
||||
for payload in presence.payloads {
|
||||
if !payload.is("x", ns::MUC_USER) {
|
||||
continue;
|
||||
}
|
||||
let muc_user = MucUser::try_from(payload)?;
|
||||
debug!("MUC user presence: {:?}", muc_user);
|
||||
for item in muc_user.items {
|
||||
if let Some(jid) = &item.jid {
|
||||
if jid == &self.jid {
|
||||
continue;
|
||||
}
|
||||
let participant = Participant {
|
||||
jid: jid.clone(),
|
||||
muc_jid: from.clone(),
|
||||
nick: item.nick,
|
||||
bin: None,
|
||||
};
|
||||
if locked_inner
|
||||
.participants
|
||||
.insert(from.resource.clone(), participant.clone())
|
||||
.is_none()
|
||||
{
|
||||
debug!("new participant: {:?}", jid);
|
||||
if let Some(f) = &locked_inner.on_participant {
|
||||
debug!("calling on_participant with new participant");
|
||||
match f(participant).await {
|
||||
Ok(Some(bin)) => {
|
||||
bin.set_property("name", format!("participant_{}", from.resource))?;
|
||||
match self.add_bin(&bin).await {
|
||||
Ok(_) => {
|
||||
if let Some(p) = locked_inner.participants.get_mut(&from.resource) {
|
||||
p.bin = Some(bin);
|
||||
}
|
||||
},
|
||||
Err(e) => warn!("failed to add participant bin: {:?}", e),
|
||||
}
|
||||
},
|
||||
Ok(None) => {},
|
||||
Err(e) => warn!("on_participant failed: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
use std::{convert::TryFrom, fmt, future::Future, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use futures::{
|
||||
sink::{Sink, SinkExt},
|
||||
stream::{Stream, StreamExt, TryStreamExt},
|
||||
};
|
||||
use maplit::hashmap;
|
||||
use tokio::sync::{mpsc, oneshot, Mutex};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio_tungstenite::tungstenite::{
|
||||
http::{Request, Uri},
|
||||
Message,
|
||||
};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use uuid::Uuid;
|
||||
use xmpp_parsers::{
|
||||
bind::{BindQuery, BindResponse},
|
||||
disco::{DiscoInfoQuery, DiscoInfoResult},
|
||||
iq::{Iq, IqType},
|
||||
sasl::{Auth, Mechanism, Success},
|
||||
websocket::Open,
|
||||
BareJid, Element, FullJid, Jid,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
conference::{JitsiConference, JitsiConferenceConfig},
|
||||
pinger::Pinger,
|
||||
stanza_filter::StanzaFilter,
|
||||
util::generate_id,
|
||||
xmpp,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum JitsiConnectionState {
|
||||
OpeningPreAuthentication,
|
||||
ReceivingFeaturesPreAuthentication,
|
||||
Authenticating,
|
||||
OpeningPostAuthentication,
|
||||
ReceivingFeaturesPostAuthentication,
|
||||
Binding,
|
||||
Discovering,
|
||||
DiscoveringExternalServices,
|
||||
Idle,
|
||||
}
|
||||
|
||||
struct JitsiConnectionInner {
|
||||
state: JitsiConnectionState,
|
||||
xmpp_domain: BareJid,
|
||||
jid: Option<FullJid>,
|
||||
external_services: Vec<xmpp::extdisco::Service>,
|
||||
connected_tx: Option<oneshot::Sender<Result<()>>>,
|
||||
stanza_filters: Vec<Box<dyn StanzaFilter + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for JitsiConnectionInner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("JitsiConnectionInner")
|
||||
.field("state", &self.state)
|
||||
.field("xmpp_domain", &self.xmpp_domain)
|
||||
.field("jid", &self.jid)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JitsiConnection {
|
||||
tx: mpsc::Sender<Element>,
|
||||
inner: Arc<Mutex<JitsiConnectionInner>>,
|
||||
}
|
||||
|
||||
impl JitsiConnection {
|
||||
pub async fn new(
|
||||
websocket_url: &str,
|
||||
xmpp_domain: &str,
|
||||
) -> Result<(Self, impl Future<Output = ()>)> {
|
||||
let websocket_url: Uri = websocket_url.parse()?;
|
||||
let xmpp_domain: BareJid = xmpp_domain.parse()?;
|
||||
|
||||
info!("Connecting XMPP WebSocket to {}", websocket_url);
|
||||
let request = Request::get(websocket_url)
|
||||
.header("Sec-Websocket-Protocol", "xmpp")
|
||||
.body(())?;
|
||||
let (websocket, _response) = tokio_tungstenite::connect_async(request).await?;
|
||||
let (sink, stream) = websocket.split();
|
||||
let (tx, rx) = mpsc::channel(64);
|
||||
|
||||
let inner = Arc::new(Mutex::new(JitsiConnectionInner {
|
||||
state: JitsiConnectionState::OpeningPreAuthentication,
|
||||
xmpp_domain,
|
||||
jid: None,
|
||||
external_services: vec![],
|
||||
connected_tx: None,
|
||||
stanza_filters: vec![],
|
||||
}));
|
||||
|
||||
let connection = Self {
|
||||
tx: tx.clone(),
|
||||
inner: inner.clone(),
|
||||
};
|
||||
|
||||
let writer = JitsiConnection::write_loop(rx, sink);
|
||||
let reader = JitsiConnection::read_loop(inner, tx, stream);
|
||||
|
||||
let background = async move {
|
||||
tokio::select! {
|
||||
res = reader => if let Err(e) = res { error!("fatal (in read loop): {:?}", e) },
|
||||
res = writer => if let Err(e) = res { error!("fatal (in write loop): {:?}", e) },
|
||||
}
|
||||
};
|
||||
|
||||
Ok((connection, background))
|
||||
}
|
||||
|
||||
pub async fn connect(&self) -> Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
{
|
||||
let mut locked_inner = self.inner.lock().await;
|
||||
locked_inner.connected_tx = Some(tx);
|
||||
let open = Open::new(locked_inner.xmpp_domain.clone());
|
||||
self.tx.send(open.into()).await?;
|
||||
}
|
||||
|
||||
rx.await?
|
||||
}
|
||||
|
||||
pub async fn join_conference(
|
||||
&self,
|
||||
glib_main_context: glib::MainContext,
|
||||
config: JitsiConferenceConfig,
|
||||
) -> Result<JitsiConference> {
|
||||
let conference_stanza = xmpp::jitsi::Conference {
|
||||
machine_uid: Uuid::new_v4().to_string(),
|
||||
room: config.muc.to_string(),
|
||||
properties: hashmap! {
|
||||
// Disable voice processing
|
||||
"stereo".to_string() => "true".to_string(),
|
||||
"startBitrate".to_string() => "800".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let iq =
|
||||
Iq::from_set(generate_id(), conference_stanza).with_to(Jid::Full(config.focus.clone()));
|
||||
self.tx.send(iq.into()).await?;
|
||||
|
||||
let conference = {
|
||||
let mut locked_inner = self.inner.lock().await;
|
||||
let conference = JitsiConference::new(
|
||||
glib_main_context,
|
||||
locked_inner
|
||||
.jid
|
||||
.as_ref()
|
||||
.context("not connected (no jid)")?
|
||||
.clone(),
|
||||
self.tx.clone(),
|
||||
config,
|
||||
locked_inner.external_services.clone(),
|
||||
)
|
||||
.await?;
|
||||
locked_inner
|
||||
.stanza_filters
|
||||
.push(Box::new(conference.clone()));
|
||||
conference
|
||||
};
|
||||
|
||||
conference.connected().await?;
|
||||
|
||||
Ok(conference)
|
||||
}
|
||||
|
||||
async fn write_loop<S>(rx: mpsc::Receiver<Element>, mut sink: S) -> Result<()>
|
||||
where
|
||||
S: Sink<Message> + Unpin,
|
||||
S::Error: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
let mut rx = ReceiverStream::new(rx);
|
||||
while let Some(element) = rx.next().await {
|
||||
let mut bytes = Vec::new();
|
||||
element.write_to(&mut bytes)?;
|
||||
let xml = String::from_utf8(bytes)?;
|
||||
debug!("XMPP >>> {}", xml);
|
||||
sink.send(Message::Text(xml)).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_loop<S>(
|
||||
inner: Arc<Mutex<JitsiConnectionInner>>,
|
||||
tx: mpsc::Sender<Element>,
|
||||
mut stream: S,
|
||||
) -> Result<()>
|
||||
where
|
||||
S: Stream<Item = std::result::Result<Message, tokio_tungstenite::tungstenite::Error>> + Unpin,
|
||||
{
|
||||
loop {
|
||||
let message = stream
|
||||
.try_next()
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("unexpected EOF"))?;
|
||||
let element: Element = match message {
|
||||
Message::Text(xml) => {
|
||||
debug!("XMPP <<< {}", xml);
|
||||
xml.parse()?
|
||||
},
|
||||
_ => {
|
||||
warn!(
|
||||
"unexpected non-text message on XMPP WebSocket stream: {:?}",
|
||||
message
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
let mut locked_inner = inner.lock().await;
|
||||
|
||||
use JitsiConnectionState::*;
|
||||
match locked_inner.state {
|
||||
OpeningPreAuthentication => {
|
||||
Open::try_from(element)?;
|
||||
info!("Connected XMPP WebSocket");
|
||||
locked_inner.state = ReceivingFeaturesPreAuthentication;
|
||||
},
|
||||
ReceivingFeaturesPreAuthentication => {
|
||||
let auth = Auth {
|
||||
mechanism: Mechanism::Anonymous,
|
||||
data: vec![],
|
||||
};
|
||||
tx.send(auth.into()).await?;
|
||||
locked_inner.state = Authenticating;
|
||||
},
|
||||
Authenticating => {
|
||||
Success::try_from(element)?;
|
||||
|
||||
let open = Open::new(locked_inner.xmpp_domain.clone());
|
||||
tx.send(open.into()).await?;
|
||||
locked_inner.state = OpeningPostAuthentication;
|
||||
},
|
||||
OpeningPostAuthentication => {
|
||||
Open::try_from(element)?;
|
||||
info!("Logged in anonymously");
|
||||
|
||||
locked_inner.state = ReceivingFeaturesPostAuthentication;
|
||||
},
|
||||
ReceivingFeaturesPostAuthentication => {
|
||||
let iq = Iq::from_set(generate_id(), BindQuery::new(None));
|
||||
tx.send(iq.into()).await?;
|
||||
locked_inner.state = Binding;
|
||||
},
|
||||
Binding => {
|
||||
let iq = Iq::try_from(element)?;
|
||||
let jid = if let IqType::Result(Some(element)) = iq.payload {
|
||||
let bind = BindResponse::try_from(element)?;
|
||||
FullJid::try_from(bind)?
|
||||
}
|
||||
else {
|
||||
bail!("bind failed");
|
||||
};
|
||||
info!("My JID: {}", jid);
|
||||
locked_inner.jid = Some(jid.clone());
|
||||
|
||||
locked_inner.stanza_filters.push(Box::new(Pinger {
|
||||
jid: jid.clone(),
|
||||
tx: tx.clone(),
|
||||
}));
|
||||
|
||||
let iq = Iq::from_get(generate_id(), DiscoInfoQuery { node: None })
|
||||
.with_from(Jid::Full(jid.clone()))
|
||||
.with_to(Jid::Bare(locked_inner.xmpp_domain.clone()));
|
||||
tx.send(iq.into()).await?;
|
||||
locked_inner.state = Discovering;
|
||||
},
|
||||
Discovering => {
|
||||
let iq = Iq::try_from(element)?;
|
||||
if let IqType::Result(Some(element)) = iq.payload {
|
||||
let _disco_info = DiscoInfoResult::try_from(element)?;
|
||||
}
|
||||
else {
|
||||
bail!("disco failed");
|
||||
}
|
||||
|
||||
let iq = Iq::from_get(generate_id(), xmpp::extdisco::ServicesQuery {})
|
||||
.with_from(Jid::Full(locked_inner.jid.as_ref().context("missing jid")?.clone()))
|
||||
.with_to(Jid::Bare(locked_inner.xmpp_domain.clone()));
|
||||
tx.send(iq.into()).await?;
|
||||
locked_inner.state = DiscoveringExternalServices;
|
||||
},
|
||||
DiscoveringExternalServices => {
|
||||
let iq = Iq::try_from(element)?;
|
||||
if let IqType::Result(Some(element)) = iq.payload {
|
||||
let services = xmpp::extdisco::ServicesResult::try_from(element)?;
|
||||
debug!("external services: {:?}", services.services);
|
||||
locked_inner.external_services = services.services;
|
||||
}
|
||||
else {
|
||||
bail!("extdisco failed");
|
||||
}
|
||||
|
||||
if let Some(tx) = locked_inner.connected_tx.take() {
|
||||
tx.send(Ok(())).map_err(|_| anyhow!("channel closed"))?;
|
||||
}
|
||||
locked_inner.state = Idle;
|
||||
},
|
||||
Idle => {
|
||||
for filter in &locked_inner.stanza_filters {
|
||||
if filter.filter(&element) {
|
||||
filter.take(element).await?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,827 @@
|
|||
use std::{collections::HashMap, fmt, net::SocketAddr};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use futures::stream::StreamExt;
|
||||
use glib::{Cast, ObjectExt, ToValue};
|
||||
use gstreamer::prelude::{ElementExt, GObjectExtManualGst, GstBinExt, PadExt};
|
||||
use nice_gst_meet as nice;
|
||||
use pem::Pem;
|
||||
use rand::random;
|
||||
use rcgen::{Certificate, CertificateParams, PKCS_ECDSA_P256_SHA256};
|
||||
use ring::digest::{digest, SHA256};
|
||||
use tokio::{
|
||||
net::lookup_host,
|
||||
runtime::Handle,
|
||||
sync::{mpsc, oneshot},
|
||||
};
|
||||
use tracing::{debug, error, warn};
|
||||
use uuid::Uuid;
|
||||
use xmpp_parsers::{
|
||||
hashes::Algo,
|
||||
iq::Iq,
|
||||
jingle::{Action, Content, Creator, Description, Jingle, Senders, Transport},
|
||||
jingle_dtls_srtp::{Fingerprint, Setup},
|
||||
jingle_ice_udp::{self, Transport as IceUdpTransport},
|
||||
jingle_rtp::{Description as RtpDescription, PayloadType, RtcpMux},
|
||||
jingle_ssma::{self, Parameter},
|
||||
Jid,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
conference::JitsiConference,
|
||||
source::{MediaType, Source},
|
||||
util::generate_id,
|
||||
};
|
||||
|
||||
const DEFAULT_STUN_PORT: u16 = 3478;
|
||||
|
||||
pub(crate) struct JingleSession {
|
||||
pipeline: gstreamer::Pipeline,
|
||||
audio_sink_element: gstreamer::Element,
|
||||
video_sink_element: gstreamer::Element,
|
||||
remote_ssrc_map: HashMap<u32, Source>,
|
||||
ice_agent: nice::Agent,
|
||||
ice_stream_id: u32,
|
||||
ice_component_id: u32,
|
||||
pub(crate) accept_iq_id: Option<String>,
|
||||
pub(crate) colibri_url: Option<String>,
|
||||
pub(crate) colibri_tx: Option<
|
||||
mpsc::Sender<
|
||||
Result<tokio_tungstenite::tungstenite::Message, tokio_tungstenite::tungstenite::Error>,
|
||||
>,
|
||||
>,
|
||||
pipeline_state_null_rx: oneshot::Receiver<()>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for JingleSession {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("JingleSession").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl JingleSession {
|
||||
pub(crate) fn pipeline(&self) -> gstreamer::Pipeline {
|
||||
self.pipeline.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn audio_sink_element(&self) -> gstreamer::Element {
|
||||
self.audio_sink_element.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn video_sink_element(&self) -> gstreamer::Element {
|
||||
self.video_sink_element.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn pause_all_sinks(&self) {
|
||||
if let Some(rtpbin) = self.pipeline.by_name("rtpbin") {
|
||||
rtpbin.foreach_src_pad(|_, pad| {
|
||||
let pad_name: String = pad.property("name").unwrap().get().unwrap();
|
||||
if pad_name.starts_with("recv_rtp_src_0_") {
|
||||
if let Some(peer_pad) = pad.peer() {
|
||||
if let Some(element) = peer_pad.parent_element() {
|
||||
element.set_state(gstreamer::State::Paused).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn pipeline_stopped(self) -> Result<()> {
|
||||
Ok(self.pipeline_state_null_rx.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn initiate(conference: &JitsiConference, jingle: Jingle) -> Result<Self> {
|
||||
let initiator = jingle
|
||||
.initiator
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("session-initiate with no initiator"))?
|
||||
.clone();
|
||||
|
||||
debug!("Received Jingle session-initiate from {}", initiator);
|
||||
|
||||
let mut ice_remote_candidates = None;
|
||||
let mut ice_remote_ufrag = None;
|
||||
let mut ice_remote_pwd = None;
|
||||
let mut dtls_fingerprint = None;
|
||||
let mut opus_payload_type = None;
|
||||
let mut h264_payload_type = None;
|
||||
let mut vp8_payload_type = None;
|
||||
let mut vp9_payload_type = None;
|
||||
let mut colibri_url = None;
|
||||
|
||||
let mut remote_ssrc_map = HashMap::new();
|
||||
|
||||
for content in &jingle.contents {
|
||||
if let Some(Description::Rtp(description)) = &content.description {
|
||||
if description.media == "audio" {
|
||||
opus_payload_type = description
|
||||
.payload_types
|
||||
.iter()
|
||||
.find(|pt| pt.name.as_deref() == Some("opus"))
|
||||
.map(|pt| pt.id);
|
||||
}
|
||||
else if description.media == "video" {
|
||||
h264_payload_type = description
|
||||
.payload_types
|
||||
.iter()
|
||||
.find(|pt| pt.name.as_deref() == Some("H264"))
|
||||
.map(|pt| pt.id);
|
||||
vp8_payload_type = description
|
||||
.payload_types
|
||||
.iter()
|
||||
.find(|pt| pt.name.as_deref() == Some("VP8"))
|
||||
.map(|pt| pt.id);
|
||||
vp9_payload_type = description
|
||||
.payload_types
|
||||
.iter()
|
||||
.find(|pt| pt.name.as_deref() == Some("VP9"))
|
||||
.map(|pt| pt.id);
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ssrc in &description.ssrcs {
|
||||
let owner = ssrc
|
||||
.info
|
||||
.as_ref()
|
||||
.context("missing ssrc-info")?
|
||||
.owner
|
||||
.clone();
|
||||
if owner == "jvb" {
|
||||
debug!("skipping ssrc (owner = jvb)");
|
||||
continue;
|
||||
}
|
||||
|
||||
remote_ssrc_map.insert(
|
||||
ssrc.id.parse()?,
|
||||
Source {
|
||||
ssrc: ssrc.id.parse()?,
|
||||
participant_id: owner
|
||||
.split('/')
|
||||
.nth(1)
|
||||
.context("invalid ssrc-info owner")?
|
||||
.to_owned(),
|
||||
media_type: if description.media == "audio" {
|
||||
MediaType::Audio
|
||||
}
|
||||
else {
|
||||
MediaType::Video
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Transport::IceUdp(transport)) = &content.transport {
|
||||
if !transport.candidates.is_empty() {
|
||||
ice_remote_candidates = Some(transport.candidates.clone());
|
||||
}
|
||||
if let Some(ufrag) = &transport.ufrag {
|
||||
ice_remote_ufrag = Some(ufrag.to_owned());
|
||||
}
|
||||
if let Some(pwd) = &transport.pwd {
|
||||
ice_remote_pwd = Some(pwd.to_owned());
|
||||
}
|
||||
if let Some(fingerprint) = &transport.fingerprint {
|
||||
if fingerprint.hash != Algo::Sha_256 {
|
||||
bail!("unsupported fingerprint hash: {:?}", fingerprint.hash);
|
||||
}
|
||||
dtls_fingerprint = Some(fingerprint.value.clone());
|
||||
}
|
||||
if let Some(websocket) = &transport.web_socket {
|
||||
colibri_url = Some(websocket.url.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(remote_fingerprint) = dtls_fingerprint {
|
||||
warn!("Remote DTLS fingerprint (verification not implemented yet): {:?}", remote_fingerprint);
|
||||
}
|
||||
|
||||
let mut dtls_cert_params = CertificateParams::new(vec!["gst-meet".to_owned()]);
|
||||
dtls_cert_params.alg = &PKCS_ECDSA_P256_SHA256;
|
||||
let dtls_cert = Certificate::from_params(dtls_cert_params)?;
|
||||
let dtls_cert_der = dtls_cert.serialize_der()?;
|
||||
let fingerprint = digest(&SHA256, &dtls_cert_der).as_ref().to_vec();
|
||||
let fingerprint_str =
|
||||
itertools::join(fingerprint.iter().map(|byte| format!("{:X}", byte)), ":");
|
||||
let dtls_cert_pem = pem::encode(&Pem {
|
||||
tag: "CERTIFICATE".to_string(),
|
||||
contents: dtls_cert_der,
|
||||
});
|
||||
let dtls_private_key_pem = pem::encode(&Pem {
|
||||
tag: "PRIVATE KEY".to_string(),
|
||||
contents: dtls_cert.serialize_private_key_der(),
|
||||
});
|
||||
debug!("Local DTLS certificate:\n{}", dtls_cert_pem);
|
||||
debug!("Local DTLS fingerprint: {}", fingerprint_str);
|
||||
|
||||
let audio_ssrc: u32 = random();
|
||||
let video_ssrc: u32 = random();
|
||||
|
||||
debug!("audio SSRC: {}", audio_ssrc);
|
||||
debug!("video SSRC: {}", video_ssrc);
|
||||
|
||||
let maybe_stun = conference
|
||||
.external_services
|
||||
.iter()
|
||||
.find(|svc| svc.r#type == "stun");
|
||||
|
||||
let stun_addr = if let Some(stun) = maybe_stun {
|
||||
lookup_host(format!("{}:{}", stun.host, stun.port.unwrap_or(DEFAULT_STUN_PORT)))
|
||||
.await?
|
||||
.next()
|
||||
}
|
||||
else {
|
||||
None
|
||||
};
|
||||
debug!("STUN address: {:?}", stun_addr);
|
||||
|
||||
let ice_agent = nice::Agent::new(&conference.glib_main_context, nice::Compatibility::Rfc5245);
|
||||
ice_agent.set_ice_tcp(false);
|
||||
if let Some((stun_addr, stun_port)) = stun_addr.map(|sa| (sa.ip().to_string(), sa.port())) {
|
||||
ice_agent.set_stun_server(Some(&stun_addr));
|
||||
ice_agent.set_stun_server_port(stun_port as u32);
|
||||
}
|
||||
ice_agent.set_upnp(false);
|
||||
ice_agent.connect_component_state_changed(|_, a, b, c| {
|
||||
debug!("ICE component-state-changed {} {} {}", a, b, c);
|
||||
});
|
||||
ice_agent.connect_new_selected_pair(|_, a, b, c, d| {
|
||||
debug!("ICE new-selected-pair {} {} {} {}", a, b, c, d);
|
||||
});
|
||||
let ice_stream_id = ice_agent.add_stream(1);
|
||||
let ice_component_id = 1;
|
||||
|
||||
if !ice_agent.attach_recv(
|
||||
ice_stream_id,
|
||||
ice_component_id,
|
||||
&conference.glib_main_context,
|
||||
|_, _, _, s| debug!("ICE nice_agent_attach_recv cb: {}", s),
|
||||
) {
|
||||
warn!("nice_agent_attach_recv failed");
|
||||
}
|
||||
|
||||
debug!("ice_agent={:?}", ice_agent);
|
||||
debug!("ice_stream_id={}", ice_stream_id);
|
||||
debug!("ice_component_id={}", ice_component_id);
|
||||
|
||||
let (ice_local_ufrag, ice_local_pwd) = ice_agent
|
||||
.local_credentials(ice_stream_id)
|
||||
.context("no local ICE credentials")?;
|
||||
|
||||
if let (Some(ufrag), Some(pwd)) = (&ice_remote_ufrag, &ice_remote_pwd) {
|
||||
debug!("setting ICE remote credentials");
|
||||
if !ice_agent.set_remote_credentials(ice_stream_id, ufrag, pwd) {
|
||||
warn!("nice_agent_set_remote_candidates failed");
|
||||
}
|
||||
}
|
||||
|
||||
ice_agent.connect_candidate_gathering_done(move |ice_agent, a| {
|
||||
debug!("ICE candidate-gathering-done {}", a);
|
||||
});
|
||||
|
||||
debug!("gathering ICE candidates");
|
||||
if !ice_agent.gather_candidates(ice_stream_id) {
|
||||
warn!("nice_agent_gather_candidates failed");
|
||||
}
|
||||
|
||||
if let (Some(ufrag), Some(pwd), Some(remote_candidates)) =
|
||||
(&ice_remote_ufrag, &ice_remote_pwd, &ice_remote_candidates)
|
||||
{
|
||||
debug!("setting ICE remote candidates: {:?}", remote_candidates);
|
||||
let remote_candidates: Vec<_> = remote_candidates
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let mut candidate = nice::Candidate::new(match c.type_ {
|
||||
jingle_ice_udp::Type::Host => nice::CandidateType::Host,
|
||||
jingle_ice_udp::Type::Prflx => nice::CandidateType::PeerReflexive,
|
||||
jingle_ice_udp::Type::Srflx => nice::CandidateType::ServerReflexive,
|
||||
jingle_ice_udp::Type::Relay => nice::CandidateType::Relayed,
|
||||
});
|
||||
candidate.set_stream_id(ice_stream_id);
|
||||
candidate.set_component_id(c.component as u32);
|
||||
candidate.set_foundation(&c.foundation);
|
||||
candidate.set_addr(SocketAddr::new(c.ip, c.port));
|
||||
candidate.set_priority(c.priority);
|
||||
candidate.set_transport(match c.protocol.as_str() {
|
||||
"udp" => nice::CandidateTransport::Udp,
|
||||
other => panic!("unsupported protocol: {}", other),
|
||||
});
|
||||
candidate.set_username(ufrag);
|
||||
candidate.set_password(pwd);
|
||||
debug!("candidate: {:?}", candidate);
|
||||
candidate
|
||||
})
|
||||
.collect();
|
||||
let candidate_refs: Vec<_> = remote_candidates.iter().collect();
|
||||
let res = ice_agent.set_remote_candidates(ice_stream_id, ice_component_id, &candidate_refs);
|
||||
if res < remote_candidates.len() as i32 {
|
||||
warn!("some remote candidates failed to add: {}", res);
|
||||
}
|
||||
}
|
||||
|
||||
let pipeline_spec = format!(
|
||||
r#"
|
||||
rtpbin rtp-profile=savpf name=rtpbin
|
||||
|
||||
nicesrc stream={0} component={1} name=nicesrc ! dtlssrtpdec name=dtlssrtpdec connection-id=gst-meet
|
||||
dtlssrtpenc name=dtlssrtpenc connection-id=gst-meet is-client=true ! nicesink stream={0} component={1} name=nicesink
|
||||
|
||||
rtpbin.send_rtp_src_0 ! dtlssrtpenc.rtp_sink_0
|
||||
rtpbin.send_rtcp_src_0 ! dtlssrtpenc.rtcp_sink_0
|
||||
rtpbin.send_rtp_src_1 ! dtlssrtpenc.rtp_sink_1
|
||||
rtpbin.send_rtcp_src_1 ! dtlssrtpenc.rtcp_sink_1
|
||||
|
||||
dtlssrtpdec.rtp_src ! rtpbin.recv_rtp_sink_0
|
||||
dtlssrtpdec.rtcp_src ! rtpbin.recv_rtcp_sink_0
|
||||
"#,
|
||||
ice_stream_id, ice_component_id,
|
||||
);
|
||||
|
||||
debug!("building gstreamer pipeline:\n{}", pipeline_spec);
|
||||
|
||||
let pipeline = gstreamer::parse_launch(&pipeline_spec)?
|
||||
.downcast::<gstreamer::Pipeline>()
|
||||
.map_err(|_| anyhow!("pipeline did not parse as a pipeline"))?;
|
||||
|
||||
let rtpbin = pipeline
|
||||
.by_name("rtpbin")
|
||||
.context("no rtpbin in pipeline")?;
|
||||
|
||||
rtpbin.connect("request-pt-map", false, move |values| {
|
||||
let f = || {
|
||||
debug!("rtpbin request-pt-map {:?}", values);
|
||||
let pt = values[2].get::<u32>()? as u8;
|
||||
let maybe_caps = if Some(pt) == opus_payload_type {
|
||||
Some(gstreamer::Caps::new_simple(
|
||||
"application/x-rtp",
|
||||
&[
|
||||
("media", &"audio"),
|
||||
("encoding-name", &"OPUS"),
|
||||
("clock-rate", &48000),
|
||||
],
|
||||
))
|
||||
}
|
||||
else if Some(pt) == h264_payload_type {
|
||||
Some(gstreamer::Caps::new_simple(
|
||||
"application/x-rtp",
|
||||
&[
|
||||
("media", &"video"),
|
||||
("encoding-name", &"H264"),
|
||||
("clock-rate", &90000),
|
||||
],
|
||||
))
|
||||
}
|
||||
else if Some(pt) == vp8_payload_type {
|
||||
Some(gstreamer::Caps::new_simple(
|
||||
"application/x-rtp",
|
||||
&[
|
||||
("media", &"video"),
|
||||
("encoding-name", &"VP8"),
|
||||
("clock-rate", &90000),
|
||||
],
|
||||
))
|
||||
}
|
||||
else if Some(pt) == vp9_payload_type {
|
||||
Some(gstreamer::Caps::new_simple(
|
||||
"application/x-rtp",
|
||||
&[
|
||||
("media", &"video"),
|
||||
("encoding-name", &"VP9"),
|
||||
("clock-rate", &90000),
|
||||
],
|
||||
))
|
||||
}
|
||||
else {
|
||||
warn!("unknown payload type: {}", pt);
|
||||
None
|
||||
};
|
||||
Ok::<_, anyhow::Error>(maybe_caps)
|
||||
};
|
||||
match f() {
|
||||
Ok(Some(caps)) => {
|
||||
debug!("mapped pt to caps: {:?}", caps);
|
||||
Some(caps.to_value())
|
||||
},
|
||||
Ok(None) => None,
|
||||
Err(e) => {
|
||||
error!("handling request-pt-map: {:?}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
})?;
|
||||
|
||||
let handle = Handle::current();
|
||||
let inner_ = conference.inner.clone();
|
||||
let pipeline_ = pipeline.clone();
|
||||
let rtpbin_ = rtpbin.clone();
|
||||
rtpbin.connect("pad-added", false, move |values| {
|
||||
let inner_ = inner_.clone();
|
||||
let handle = handle.clone();
|
||||
let pipeline_ = pipeline_.clone();
|
||||
let rtpbin_ = rtpbin_.clone();
|
||||
let f = move || {
|
||||
debug!("rtpbin pad-added {:?}", values);
|
||||
let pad: gstreamer::Pad = values[1].get()?;
|
||||
let pad_name: String = pad.property("name")?.get()?;
|
||||
if pad_name.starts_with("recv_rtp_src_0_") {
|
||||
let mut parts = pad_name.split('_').skip(4);
|
||||
let ssrc: u32 = parts.next().context("malformed pad name")?.parse()?;
|
||||
let pt: u8 = parts.next().context("malformed pad name")?.parse()?;
|
||||
let source = handle.block_on(async move {
|
||||
let locked_inner = inner_.lock().await;
|
||||
let jingle_session = locked_inner
|
||||
.jingle_session
|
||||
.as_ref()
|
||||
.context("not connected (no jingle session)")?;
|
||||
Ok::<_, anyhow::Error>(
|
||||
jingle_session
|
||||
.remote_ssrc_map
|
||||
.get(&ssrc)
|
||||
.context(format!("unknown ssrc: {}", ssrc))?
|
||||
.clone(),
|
||||
)
|
||||
})?;
|
||||
|
||||
debug!("pad added for remote source: {:?}", source);
|
||||
|
||||
let element_name = match source.media_type {
|
||||
MediaType::Audio => {
|
||||
if Some(pt) == opus_payload_type {
|
||||
"rtpopusdepay"
|
||||
}
|
||||
else {
|
||||
bail!("received audio with unsupported PT {}", pt);
|
||||
}
|
||||
},
|
||||
MediaType::Video => {
|
||||
if Some(pt) == h264_payload_type {
|
||||
"rtph264depay"
|
||||
}
|
||||
else if Some(pt) == vp8_payload_type {
|
||||
"rtpvp8depay"
|
||||
}
|
||||
else if Some(pt) == vp9_payload_type {
|
||||
"rtpvp9depay"
|
||||
}
|
||||
else {
|
||||
bail!("received video with unsupported PT {}", pt);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let source_element = gstreamer::ElementFactory::make(element_name, None)?;
|
||||
pipeline_
|
||||
.add(&source_element)
|
||||
.context(format!("failed to add {} to pipeline", element_name))?;
|
||||
source_element.sync_state_with_parent()?;
|
||||
debug!("created {} element", element_name);
|
||||
rtpbin_
|
||||
.link_pads(Some(&pad_name), &source_element, None)
|
||||
.context(format!(
|
||||
"failed to link rtpbin.{} to {}",
|
||||
pad_name, element_name
|
||||
))?;
|
||||
debug!("linked rtpbin.{} to {}", pad_name, element_name);
|
||||
|
||||
let src_pad = source_element
|
||||
.static_pad("src")
|
||||
.context("depayloader has no src pad")?;
|
||||
|
||||
if let Some(participant_bin) =
|
||||
pipeline_.by_name(&format!("participant_{}", source.participant_id))
|
||||
{
|
||||
let sink_pad_name = match source.media_type {
|
||||
MediaType::Audio => "audio",
|
||||
MediaType::Video => "video",
|
||||
};
|
||||
if let Some(sink_pad) = participant_bin.static_pad(sink_pad_name) {
|
||||
debug!("linking depayloader to participant bin");
|
||||
src_pad.link(&sink_pad)?;
|
||||
}
|
||||
else {
|
||||
warn!(
|
||||
"no {} sink pad in {} participant bin",
|
||||
sink_pad_name, source.participant_id
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug!("no participant bin for {}", source.participant_id);
|
||||
}
|
||||
|
||||
if !src_pad.is_linked() {
|
||||
debug!("nothing linked to {}, adding fakesink", element_name);
|
||||
let fakesink = gstreamer::ElementFactory::make("fakesink", None)?;
|
||||
pipeline_.add(&fakesink)?;
|
||||
fakesink.sync_state_with_parent()?;
|
||||
source_element.link(&fakesink)?;
|
||||
}
|
||||
|
||||
gstreamer::debug_bin_to_dot_file(
|
||||
&pipeline_,
|
||||
gstreamer::DebugGraphDetails::ALL,
|
||||
&format!("ssrc-added-{}", ssrc),
|
||||
);
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
else {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
if let Err(e) = f() {
|
||||
error!("handling pad-added: {:?}", e);
|
||||
}
|
||||
None
|
||||
})?;
|
||||
|
||||
let audio_sink_element = gstreamer::ElementFactory::make("rtpopuspay", None)?;
|
||||
audio_sink_element.set_property(
|
||||
"pt",
|
||||
opus_payload_type.context("no opus payload type in jingle session-initiate")? as u32,
|
||||
)?;
|
||||
audio_sink_element.set_property("min-ptime", 10i64 * 1000 * 1000)?;
|
||||
audio_sink_element.set_property("ssrc", audio_ssrc)?;
|
||||
pipeline.add(&audio_sink_element)?;
|
||||
audio_sink_element.link_pads(None, &rtpbin, Some("send_rtp_sink_0"))?;
|
||||
|
||||
let video_sink_element = match conference.config.video_codec.as_str() {
|
||||
"h264" => {
|
||||
let element = gstreamer::ElementFactory::make("rtph264pay", None)?;
|
||||
element.set_property(
|
||||
"pt",
|
||||
h264_payload_type.context("no h264 payload type in jingle session-initiate")? as u32,
|
||||
)?;
|
||||
element.set_property_from_str("aggregate-mode", "zero-latency");
|
||||
element
|
||||
},
|
||||
"vp8" => {
|
||||
let element = gstreamer::ElementFactory::make("rtpvp8pay", None)?;
|
||||
element.set_property(
|
||||
"pt",
|
||||
vp8_payload_type.context("no vp8 payload type in jingle session-initiate")? as u32,
|
||||
)?;
|
||||
element.set_property_from_str("picture-id-mode", "15-bit");
|
||||
element
|
||||
},
|
||||
"vp9" => {
|
||||
let element = gstreamer::ElementFactory::make("rtpvp9pay", None)?;
|
||||
element.set_property(
|
||||
"pt",
|
||||
vp9_payload_type.context("no vp9 payload type in jingle session-initiate")? as u32,
|
||||
)?;
|
||||
element.set_property_from_str("picture-id-mode", "15-bit");
|
||||
element
|
||||
},
|
||||
other => bail!("unsupported video codec: {}", other),
|
||||
};
|
||||
video_sink_element.set_property("ssrc", video_ssrc)?;
|
||||
pipeline.add(&video_sink_element)?;
|
||||
video_sink_element.link_pads(None, &rtpbin, Some("send_rtp_sink_1"))?;
|
||||
|
||||
let dtlssrtpdec = pipeline
|
||||
.by_name("dtlssrtpdec")
|
||||
.context("no dtlssrtpdec in pipeline")?;
|
||||
dtlssrtpdec.set_property(
|
||||
"pem",
|
||||
format!("{}\n{}", dtls_cert_pem, dtls_private_key_pem),
|
||||
)?;
|
||||
|
||||
let nicesrc = pipeline
|
||||
.by_name("nicesrc")
|
||||
.context("no nicesrc in pipeline")?;
|
||||
nicesrc.set_property("agent", &ice_agent)?;
|
||||
|
||||
let nicesink = pipeline
|
||||
.by_name("nicesink")
|
||||
.context("no nicesink in pipeline")?;
|
||||
nicesink.set_property("agent", &ice_agent)?;
|
||||
|
||||
let bus = pipeline.bus().context("failed to get pipeline bus")?;
|
||||
|
||||
let (pipeline_state_null_tx, pipeline_state_null_rx) = oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
let mut stream = bus.stream();
|
||||
while let Some(msg) = stream.next().await {
|
||||
match msg.view() {
|
||||
gstreamer::MessageView::Error(e) => {
|
||||
if let Some(d) = e.debug() {
|
||||
error!("{}", d);
|
||||
}
|
||||
},
|
||||
gstreamer::MessageView::Warning(e) => {
|
||||
if let Some(d) = e.debug() {
|
||||
warn!("{}", d);
|
||||
}
|
||||
},
|
||||
gstreamer::MessageView::StateChanged(state)
|
||||
if state.current() == gstreamer::State::Null =>
|
||||
{
|
||||
debug!("pipeline state is null");
|
||||
pipeline_state_null_tx.send(()).unwrap();
|
||||
break;
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
gstreamer::debug_bin_to_dot_file(
|
||||
&pipeline,
|
||||
gstreamer::DebugGraphDetails::ALL,
|
||||
"session-initiate",
|
||||
);
|
||||
|
||||
let local_candidates = ice_agent.local_candidates(ice_stream_id, ice_component_id);
|
||||
debug!("local candidates: {:?}", local_candidates);
|
||||
|
||||
debug!("building Jingle session-accept");
|
||||
let mut jingle_accept = Jingle::new(Action::SessionAccept, jingle.sid.clone())
|
||||
.with_initiator(
|
||||
jingle
|
||||
.initiator
|
||||
.as_ref()
|
||||
.context("jingle session-initiate with no initiator")?
|
||||
.clone(),
|
||||
)
|
||||
.with_responder(Jid::Full(conference.jid.clone()));
|
||||
|
||||
for initiate_content in &jingle.contents {
|
||||
let mut description = RtpDescription::new(initiate_content.name.0.clone());
|
||||
|
||||
description.payload_types = if initiate_content.name.0 == "audio" {
|
||||
vec![PayloadType::new(
|
||||
opus_payload_type.context("no opus payload type in jingle session-initiate")?,
|
||||
"opus".to_owned(),
|
||||
48000,
|
||||
2,
|
||||
)]
|
||||
}
|
||||
else {
|
||||
match conference.config.video_codec.as_str() {
|
||||
"h264" => vec![PayloadType::new(
|
||||
h264_payload_type.context("no h264 payload type in jingle session-initiate")?,
|
||||
"H264".to_owned(),
|
||||
90000,
|
||||
1,
|
||||
)],
|
||||
"vp8" => vec![PayloadType::new(
|
||||
vp8_payload_type.context("no vp8 payload type in jingle session-initiate")?,
|
||||
"VP8".to_owned(),
|
||||
90000,
|
||||
1,
|
||||
)],
|
||||
"vp9" => vec![PayloadType::new(
|
||||
vp9_payload_type.context("no vp9 payload type in jingle session-initiate")?,
|
||||
"VP9".to_owned(),
|
||||
90000,
|
||||
1,
|
||||
)],
|
||||
other => bail!("unsupported video codec: {}", other),
|
||||
}
|
||||
};
|
||||
|
||||
description.rtcp_mux = Some(RtcpMux);
|
||||
|
||||
let mslabel = Uuid::new_v4().to_string();
|
||||
let label = Uuid::new_v4().to_string();
|
||||
let cname = Uuid::new_v4().to_string();
|
||||
|
||||
let mut ssrc = jingle_ssma::Source::new(if initiate_content.name.0 == "audio" {
|
||||
audio_ssrc.to_string()
|
||||
}
|
||||
else {
|
||||
video_ssrc.to_string()
|
||||
});
|
||||
ssrc.parameters.push(Parameter {
|
||||
name: "cname".to_owned(),
|
||||
value: Some(cname),
|
||||
});
|
||||
ssrc.parameters.push(Parameter {
|
||||
name: "msid".to_owned(),
|
||||
value: Some(format!("{} {}", mslabel, label)),
|
||||
});
|
||||
ssrc.parameters.push(Parameter {
|
||||
name: "mslabel".to_owned(),
|
||||
value: Some(mslabel),
|
||||
});
|
||||
ssrc.parameters.push(Parameter {
|
||||
name: "label".to_owned(),
|
||||
value: Some(label),
|
||||
});
|
||||
description.ssrcs = vec![ssrc];
|
||||
|
||||
let mut transport = IceUdpTransport::new().with_fingerprint(Fingerprint {
|
||||
hash: Algo::Sha_256,
|
||||
setup: Some(Setup::Active),
|
||||
value: fingerprint.clone(),
|
||||
required: Some(true.to_string()),
|
||||
});
|
||||
transport.ufrag = Some(ice_local_ufrag.clone());
|
||||
transport.pwd = Some(ice_local_pwd.clone());
|
||||
transport.candidates = vec![];
|
||||
for c in &local_candidates {
|
||||
match c.transport() {
|
||||
nice::CandidateTransport::Udp => {
|
||||
let addr = c.addr();
|
||||
let foundation = c.foundation()?;
|
||||
transport.candidates.push(jingle_ice_udp::Candidate {
|
||||
component: c.component_id() as u8,
|
||||
foundation: foundation.to_owned(),
|
||||
generation: 0,
|
||||
id: Uuid::new_v4().to_string(),
|
||||
ip: addr.ip(),
|
||||
port: addr.port(),
|
||||
priority: c.priority(),
|
||||
protocol: "udp".to_owned(),
|
||||
type_: match c.type_() {
|
||||
nice::CandidateType::Host => jingle_ice_udp::Type::Host,
|
||||
nice::CandidateType::PeerReflexive => jingle_ice_udp::Type::Prflx,
|
||||
nice::CandidateType::ServerReflexive => jingle_ice_udp::Type::Srflx,
|
||||
nice::CandidateType::Relayed => jingle_ice_udp::Type::Relay,
|
||||
other => bail!("unsupported candidate type: {:?}", other),
|
||||
},
|
||||
rel_addr: None,
|
||||
rel_port: None,
|
||||
network: None,
|
||||
});
|
||||
},
|
||||
other => {
|
||||
warn!("skipping unsupported ICE transport: {:?}", other);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
jingle_accept = jingle_accept.add_content(
|
||||
Content::new(Creator::Responder, initiate_content.name.clone())
|
||||
.with_senders(Senders::Both)
|
||||
.with_description(description)
|
||||
.with_transport(transport),
|
||||
);
|
||||
}
|
||||
|
||||
let accept_iq_id = generate_id();
|
||||
let session_accept_iq = Iq::from_set(accept_iq_id.clone(), jingle_accept)
|
||||
.with_to(Jid::Full(conference.focus_jid_in_muc()?))
|
||||
.with_from(Jid::Full(conference.jid.clone()));
|
||||
conference.xmpp_tx.send(session_accept_iq.into()).await?;
|
||||
|
||||
Ok(Self {
|
||||
pipeline,
|
||||
audio_sink_element,
|
||||
video_sink_element,
|
||||
remote_ssrc_map,
|
||||
ice_agent,
|
||||
ice_stream_id,
|
||||
ice_component_id,
|
||||
accept_iq_id: Some(accept_iq_id),
|
||||
colibri_url,
|
||||
colibri_tx: None,
|
||||
pipeline_state_null_rx,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn source_add(&mut self, jingle: Jingle) -> Result<()> {
|
||||
for content in &jingle.contents {
|
||||
if let Some(Description::Rtp(description)) = &content.description {
|
||||
for ssrc in &description.ssrcs {
|
||||
let owner = ssrc
|
||||
.info
|
||||
.as_ref()
|
||||
.context("missing ssrc-info")?
|
||||
.owner
|
||||
.clone();
|
||||
if owner == "jvb" {
|
||||
debug!("skipping ssrc (owner = jvb)");
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!("adding ssrc to remote_ssrc_map: {:?}", ssrc);
|
||||
self.remote_ssrc_map.insert(
|
||||
ssrc.id.parse()?,
|
||||
Source {
|
||||
ssrc: ssrc.id.parse()?,
|
||||
participant_id: owner
|
||||
.split('/')
|
||||
.nth(1)
|
||||
.context("invalid ssrc-info owner")?
|
||||
.to_owned(),
|
||||
media_type: if description.media == "audio" {
|
||||
MediaType::Audio
|
||||
}
|
||||
else {
|
||||
MediaType::Video
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
pub mod conference;
|
||||
pub mod connection;
|
||||
mod jingle;
|
||||
mod xmpp;
|
||||
mod pinger;
|
||||
pub mod source;
|
||||
mod stanza_filter;
|
||||
mod util;
|
||||
|
||||
pub use crate::{
|
||||
conference::{JitsiConference, JitsiConferenceConfig, Participant},
|
||||
connection::JitsiConnection,
|
||||
source::{MediaType, Source},
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::mpsc;
|
||||
use xmpp_parsers::{iq::Iq, Element, FullJid, Jid};
|
||||
|
||||
use crate::stanza_filter::StanzaFilter;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Pinger {
|
||||
pub(crate) jid: FullJid,
|
||||
pub(crate) tx: mpsc::Sender<Element>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StanzaFilter for Pinger {
|
||||
#[tracing::instrument(level = "trace")]
|
||||
fn filter(&self, element: &Element) -> bool {
|
||||
element.is("iq", "jabber:client") && element.has_child("ping", "urn:xmpp:ping")
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", err)]
|
||||
async fn take(&self, element: Element) -> Result<()> {
|
||||
let iq = Iq::try_from(element)?;
|
||||
let result_iq = Iq::empty_result(
|
||||
iq.from.ok_or_else(|| anyhow!("iq missing from"))?,
|
||||
iq.id.clone(),
|
||||
)
|
||||
.with_from(Jid::Full(self.jid.clone()));
|
||||
self.tx.send(result_iq.into()).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Source {
|
||||
pub(crate) ssrc: u32,
|
||||
pub participant_id: String,
|
||||
pub media_type: MediaType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub enum MediaType {
|
||||
Video,
|
||||
Audio,
|
||||
}
|
||||
|
||||
impl MediaType {
|
||||
pub(crate) fn jitsi_muted_presence_element_name(&self) -> &'static str {
|
||||
match self {
|
||||
MediaType::Video => "videomuted",
|
||||
MediaType::Audio => "audiomuted",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use xmpp_parsers::Element;
|
||||
|
||||
#[async_trait]
|
||||
pub(crate) trait StanzaFilter {
|
||||
fn filter(&self, element: &Element) -> bool;
|
||||
async fn take(&self, element: Element) -> Result<()>;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
use std::collections::hash_map::Entry;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) fn generate_id() -> String {
|
||||
Uuid::new_v4().to_string()
|
||||
}
|
||||
|
||||
pub(crate) trait FallibleEntry<'a, V> {
|
||||
fn or_try_insert_with<E, F: FnOnce() -> Result<V, E>>(self, default: F) -> Result<&'a mut V, E>;
|
||||
}
|
||||
|
||||
impl<'a, K, V> FallibleEntry<'a, V> for Entry<'a, K, V> {
|
||||
fn or_try_insert_with<E, F: FnOnce() -> Result<V, E>>(self, default: F) -> Result<&'a mut V, E> {
|
||||
Ok(match self {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => entry.insert(default()?),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use xmpp_parsers::{
|
||||
Element,
|
||||
iq::IqGetPayload,
|
||||
};
|
||||
|
||||
use crate::xmpp::ns;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ServicesQuery {}
|
||||
|
||||
impl TryFrom<Element> for ServicesQuery {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(_elem: Element) -> Result<ServicesQuery> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServicesQuery> for Element {
|
||||
fn from(_services: ServicesQuery) -> Element {
|
||||
let builder = Element::builder("services", ns::EXTDISCO);
|
||||
builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl IqGetPayload for ServicesQuery {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Service {
|
||||
pub(crate) r#type: String,
|
||||
pub(crate) name: Option<String>,
|
||||
pub(crate) host: String,
|
||||
pub(crate) port: Option<u16>,
|
||||
pub(crate) transport: Option<String>,
|
||||
pub(crate) restricted: Option<bool>,
|
||||
pub(crate) username: Option<String>,
|
||||
pub(crate) password: Option<String>,
|
||||
pub(crate) expires: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ServicesResult {
|
||||
pub(crate) services: Vec<Service>,
|
||||
}
|
||||
|
||||
impl TryFrom<Element> for ServicesResult {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(elem: Element) -> Result<ServicesResult> {
|
||||
if !elem.is("services", ns::EXTDISCO) {
|
||||
bail!("not a services element");
|
||||
}
|
||||
Ok(ServicesResult {
|
||||
services: elem
|
||||
.children()
|
||||
.map(|child| Ok(Service {
|
||||
r#type: child.attr("type").context("missing type attr")?.to_owned(),
|
||||
name: child.attr("name").map(ToOwned::to_owned),
|
||||
host: child.attr("host").context("missing host attr")?.to_owned(),
|
||||
port: child.attr("port").map(|p| p.parse()).transpose()?,
|
||||
transport: child.attr("transport").map(ToOwned::to_owned),
|
||||
restricted: child.attr("restricted").map(|b| b.to_lowercase() == "parse" || b == "1"),
|
||||
username: child.attr("username").map(ToOwned::to_owned),
|
||||
password: child.attr("password").map(ToOwned::to_owned),
|
||||
expires: child.attr("expires").map(ToOwned::to_owned),
|
||||
}))
|
||||
.collect::<Result<_>>()?,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
use std::{collections::HashMap, convert::TryFrom};
|
||||
|
||||
use anyhow::Result;
|
||||
use xmpp_parsers::{iq::IqSetPayload, Element};
|
||||
|
||||
use crate::xmpp::ns;
|
||||
|
||||
pub(crate) struct Conference {
|
||||
pub(crate) machine_uid: String,
|
||||
pub(crate) room: String,
|
||||
pub(crate) properties: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl IqSetPayload for Conference {}
|
||||
|
||||
impl TryFrom<Element> for Conference {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(_element: Element) -> Result<Conference> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Conference> for Element {
|
||||
fn from(conference: Conference) -> Element {
|
||||
let mut builder = Element::builder("conference", ns::JITSI_FOCUS)
|
||||
.attr("machine-uid", conference.machine_uid)
|
||||
.attr("room", conference.room);
|
||||
for (name, value) in conference.properties {
|
||||
builder = builder.append(
|
||||
Element::builder("property", ns::JITSI_FOCUS)
|
||||
.attr("name", name)
|
||||
.attr("value", value)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub(crate) mod extdisco;
|
||||
pub(crate) mod jitsi;
|
||||
mod ns;
|
|
@ -0,0 +1,4 @@
|
|||
/// XEP-0215: External Service Discovery
|
||||
pub(crate) const EXTDISCO: &str = "urn:xmpp:extdisco:2";
|
||||
|
||||
pub(crate) const JITSI_FOCUS: &str = "http://jitsi.org/protocol/focus";
|
|
@ -0,0 +1,73 @@
|
|||
[package]
|
||||
name = "nice-gst-meet-sys"
|
||||
version = "0.1.0"
|
||||
links = "nice"
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
license = "MIT/Apache-2.0"
|
||||
authors = ["Jasper Hugo <jasper@avstack.io>"]
|
||||
|
||||
[package.metadata.system-deps.nice]
|
||||
name = "nice"
|
||||
version = "0.1"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_4]
|
||||
version = "0.1.4"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_5]
|
||||
version = "0.1.5"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_6]
|
||||
version = "0.1.6"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_8]
|
||||
version = "0.1.8"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_14]
|
||||
version = "0.1.14"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_15]
|
||||
version = "0.1.15"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_16]
|
||||
version = "0.1.16"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_17]
|
||||
version = "0.1.17"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_18]
|
||||
version = "0.1.18"
|
||||
|
||||
[package.metadata.system-deps.nice.v0_1_20]
|
||||
version = "0.1.20"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["dox"]
|
||||
|
||||
[lib]
|
||||
name = "nice_sys"
|
||||
|
||||
[dependencies]
|
||||
libc = { version = "0.2", default-features = false }
|
||||
glib = { version = "0.14", default-features = false }
|
||||
gio = { version = "0.14", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
system-deps = { version = "3", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
shell-words = { version = "1", default-features = false }
|
||||
tempfile = { version = "3", default-features = false }
|
||||
|
||||
[features]
|
||||
v0_1_4 = []
|
||||
v0_1_5 = ["v0_1_4"]
|
||||
v0_1_6 = ["v0_1_5"]
|
||||
v0_1_8 = ["v0_1_6"]
|
||||
v0_1_14 = ["v0_1_8"]
|
||||
v0_1_15 = ["v0_1_14"]
|
||||
v0_1_16 = ["v0_1_15"]
|
||||
v0_1_17 = ["v0_1_16"]
|
||||
v0_1_18 = ["v0_1_17"]
|
||||
v0_1_20 = ["v0_1_18"]
|
||||
dox = []
|
|
@ -0,0 +1,8 @@
|
|||
[options]
|
||||
library = "Nice"
|
||||
version = "0.1"
|
||||
min_cfg_version = "0.1"
|
||||
work_mode = "sys"
|
||||
target_path = "."
|
||||
external_libraries = []
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,23 @@
|
|||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,17 @@
|
|||
// Generated by gir (https://github.com/gtk-rs/gir @ 5bbf6cb)
|
||||
// from ../../gir-files (@ 8e47c67)
|
||||
// DO NOT EDIT
|
||||
|
||||
#[cfg(not(feature = "dox"))]
|
||||
use std::process;
|
||||
|
||||
#[cfg(feature = "dox")]
|
||||
fn main() {} // prevent linking libraries to avoid documentation failure
|
||||
|
||||
#[cfg(not(feature = "dox"))]
|
||||
fn main() {
|
||||
if let Err(s) = system_deps::Config::new().probe() {
|
||||
println!("cargo:warning={}", s);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,683 @@
|
|||
// Generated by gir (https://github.com/gtk-rs/gir @ 5bbf6cb)
|
||||
// from ../../gir-files (@ 8e47c67)
|
||||
// DO NOT EDIT
|
||||
|
||||
#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)]
|
||||
#![allow(
|
||||
clippy::approx_constant,
|
||||
clippy::type_complexity,
|
||||
clippy::unreadable_literal,
|
||||
clippy::upper_case_acronyms
|
||||
)]
|
||||
#![cfg_attr(feature = "dox", feature(doc_cfg))]
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use glib::ffi::{gboolean, gconstpointer, gpointer, GType};
|
||||
#[allow(unused_imports)]
|
||||
use libc::{
|
||||
c_char, c_double, c_float, c_int, c_long, c_short, c_uchar, c_uint, c_ulong, c_ushort, c_void,
|
||||
intptr_t, size_t, sockaddr, sockaddr_in, sockaddr_in6, ssize_t, time_t, uintptr_t, FILE,
|
||||
};
|
||||
|
||||
// Enums
|
||||
pub type NiceCandidateTransport = c_int;
|
||||
pub const NICE_CANDIDATE_TRANSPORT_UDP: NiceCandidateTransport = 0;
|
||||
pub const NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE: NiceCandidateTransport = 1;
|
||||
pub const NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE: NiceCandidateTransport = 2;
|
||||
pub const NICE_CANDIDATE_TRANSPORT_TCP_SO: NiceCandidateTransport = 3;
|
||||
|
||||
pub type NiceCandidateType = c_int;
|
||||
pub const NICE_CANDIDATE_TYPE_HOST: NiceCandidateType = 0;
|
||||
pub const NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE: NiceCandidateType = 1;
|
||||
pub const NICE_CANDIDATE_TYPE_PEER_REFLEXIVE: NiceCandidateType = 2;
|
||||
pub const NICE_CANDIDATE_TYPE_RELAYED: NiceCandidateType = 3;
|
||||
|
||||
pub type NiceCompatibility = c_int;
|
||||
pub const NICE_COMPATIBILITY_RFC5245: NiceCompatibility = 0;
|
||||
pub const NICE_COMPATIBILITY_DRAFT19: NiceCompatibility = 0;
|
||||
pub const NICE_COMPATIBILITY_GOOGLE: NiceCompatibility = 1;
|
||||
pub const NICE_COMPATIBILITY_MSN: NiceCompatibility = 2;
|
||||
pub const NICE_COMPATIBILITY_WLM2009: NiceCompatibility = 3;
|
||||
pub const NICE_COMPATIBILITY_OC2007: NiceCompatibility = 4;
|
||||
pub const NICE_COMPATIBILITY_OC2007R2: NiceCompatibility = 5;
|
||||
pub const NICE_COMPATIBILITY_LAST: NiceCompatibility = 5;
|
||||
|
||||
pub type NiceComponentState = c_int;
|
||||
pub const NICE_COMPONENT_STATE_DISCONNECTED: NiceComponentState = 0;
|
||||
pub const NICE_COMPONENT_STATE_GATHERING: NiceComponentState = 1;
|
||||
pub const NICE_COMPONENT_STATE_CONNECTING: NiceComponentState = 2;
|
||||
pub const NICE_COMPONENT_STATE_CONNECTED: NiceComponentState = 3;
|
||||
pub const NICE_COMPONENT_STATE_READY: NiceComponentState = 4;
|
||||
pub const NICE_COMPONENT_STATE_FAILED: NiceComponentState = 5;
|
||||
pub const NICE_COMPONENT_STATE_LAST: NiceComponentState = 6;
|
||||
|
||||
pub type NiceComponentType = c_int;
|
||||
pub const NICE_COMPONENT_TYPE_RTP: NiceComponentType = 1;
|
||||
pub const NICE_COMPONENT_TYPE_RTCP: NiceComponentType = 2;
|
||||
|
||||
pub type NiceNominationMode = c_int;
|
||||
pub const NICE_NOMINATION_MODE_REGULAR: NiceNominationMode = 0;
|
||||
pub const NICE_NOMINATION_MODE_AGGRESSIVE: NiceNominationMode = 1;
|
||||
|
||||
pub type NiceProxyType = c_int;
|
||||
pub const NICE_PROXY_TYPE_NONE: NiceProxyType = 0;
|
||||
pub const NICE_PROXY_TYPE_SOCKS5: NiceProxyType = 1;
|
||||
pub const NICE_PROXY_TYPE_HTTP: NiceProxyType = 2;
|
||||
pub const NICE_PROXY_TYPE_LAST: NiceProxyType = 2;
|
||||
|
||||
pub type NiceRelayType = c_int;
|
||||
pub const NICE_RELAY_TYPE_TURN_UDP: NiceRelayType = 0;
|
||||
pub const NICE_RELAY_TYPE_TURN_TCP: NiceRelayType = 1;
|
||||
pub const NICE_RELAY_TYPE_TURN_TLS: NiceRelayType = 2;
|
||||
|
||||
pub type PseudoTcpDebugLevel = c_int;
|
||||
pub const PSEUDO_TCP_DEBUG_NONE: PseudoTcpDebugLevel = 0;
|
||||
pub const PSEUDO_TCP_DEBUG_NORMAL: PseudoTcpDebugLevel = 1;
|
||||
pub const PSEUDO_TCP_DEBUG_VERBOSE: PseudoTcpDebugLevel = 2;
|
||||
|
||||
pub type PseudoTcpShutdown = c_int;
|
||||
pub const PSEUDO_TCP_SHUTDOWN_RD: PseudoTcpShutdown = 0;
|
||||
pub const PSEUDO_TCP_SHUTDOWN_WR: PseudoTcpShutdown = 1;
|
||||
pub const PSEUDO_TCP_SHUTDOWN_RDWR: PseudoTcpShutdown = 2;
|
||||
|
||||
pub type PseudoTcpState = c_int;
|
||||
pub const PSEUDO_TCP_LISTEN: PseudoTcpState = 0;
|
||||
pub const PSEUDO_TCP_SYN_SENT: PseudoTcpState = 1;
|
||||
pub const PSEUDO_TCP_SYN_RECEIVED: PseudoTcpState = 2;
|
||||
pub const PSEUDO_TCP_ESTABLISHED: PseudoTcpState = 3;
|
||||
pub const PSEUDO_TCP_CLOSED: PseudoTcpState = 4;
|
||||
pub const PSEUDO_TCP_FIN_WAIT_1: PseudoTcpState = 5;
|
||||
pub const PSEUDO_TCP_FIN_WAIT_2: PseudoTcpState = 6;
|
||||
pub const PSEUDO_TCP_CLOSING: PseudoTcpState = 7;
|
||||
pub const PSEUDO_TCP_TIME_WAIT: PseudoTcpState = 8;
|
||||
pub const PSEUDO_TCP_CLOSE_WAIT: PseudoTcpState = 9;
|
||||
pub const PSEUDO_TCP_LAST_ACK: PseudoTcpState = 10;
|
||||
|
||||
pub type PseudoTcpWriteResult = c_int;
|
||||
pub const WR_SUCCESS: PseudoTcpWriteResult = 0;
|
||||
pub const WR_TOO_LARGE: PseudoTcpWriteResult = 1;
|
||||
pub const WR_FAIL: PseudoTcpWriteResult = 2;
|
||||
|
||||
// Constants
|
||||
pub const NICE_AGENT_MAX_REMOTE_CANDIDATES: c_int = 25;
|
||||
pub const NICE_CANDIDATE_MAX_FOUNDATION: c_int = 33;
|
||||
pub const NICE_CANDIDATE_MAX_LOCAL_ADDRESSES: c_int = 64;
|
||||
pub const NICE_CANDIDATE_MAX_TURN_SERVERS: c_int = 8;
|
||||
|
||||
// Flags
|
||||
pub type NiceAgentOption = c_uint;
|
||||
pub const NICE_AGENT_OPTION_REGULAR_NOMINATION: NiceAgentOption = 1;
|
||||
pub const NICE_AGENT_OPTION_RELIABLE: NiceAgentOption = 2;
|
||||
pub const NICE_AGENT_OPTION_LITE_MODE: NiceAgentOption = 4;
|
||||
pub const NICE_AGENT_OPTION_ICE_TRICKLE: NiceAgentOption = 8;
|
||||
pub const NICE_AGENT_OPTION_SUPPORT_RENOMINATION: NiceAgentOption = 16;
|
||||
pub const NICE_AGENT_OPTION_CONSENT_FRESHNESS: NiceAgentOption = 32;
|
||||
|
||||
// Unions
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union NiceAddress_s {
|
||||
pub addr: sockaddr,
|
||||
pub ip4: sockaddr_in,
|
||||
pub ip6: sockaddr_in6,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for NiceAddress_s {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct(&format!("NiceAddress_s @ {:p}", self))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
pub type NiceAgentRecvFunc =
|
||||
Option<unsafe extern "C" fn(*mut NiceAgent, c_uint, c_uint, c_uint, *mut c_char, gpointer)>;
|
||||
|
||||
// Records
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NiceAddress {
|
||||
pub s: NiceAddress_s,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for NiceAddress {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct(&format!("NiceAddress @ {:p}", self))
|
||||
.field("s", &self.s)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NiceAgentClass {
|
||||
pub parent_class: glib::object::GObjectClass,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for NiceAgentClass {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct(&format!("NiceAgentClass @ {:p}", self))
|
||||
.field("parent_class", &self.parent_class)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NiceCandidate {
|
||||
pub type_: NiceCandidateType,
|
||||
pub transport: NiceCandidateTransport,
|
||||
pub addr: NiceAddress,
|
||||
pub base_addr: NiceAddress,
|
||||
pub priority: u32,
|
||||
pub stream_id: c_uint,
|
||||
pub component_id: c_uint,
|
||||
pub foundation: [c_char; 33],
|
||||
pub username: *mut c_char,
|
||||
pub password: *mut c_char,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for NiceCandidate {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct(&format!("NiceCandidate @ {:p}", self))
|
||||
.field("type_", &self.type_)
|
||||
.field("transport", &self.transport)
|
||||
.field("addr", &self.addr)
|
||||
.field("base_addr", &self.base_addr)
|
||||
.field("priority", &self.priority)
|
||||
.field("stream_id", &self.stream_id)
|
||||
.field("component_id", &self.component_id)
|
||||
.field("username", &self.username)
|
||||
.field("password", &self.password)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NiceInputMessage {
|
||||
pub buffers: *mut gio::ffi::GInputVector,
|
||||
pub n_buffers: c_int,
|
||||
pub from: *mut NiceAddress,
|
||||
pub length: size_t,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for NiceInputMessage {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct(&format!("NiceInputMessage @ {:p}", self))
|
||||
.field("buffers", &self.buffers)
|
||||
.field("n_buffers", &self.n_buffers)
|
||||
.field("from", &self.from)
|
||||
.field("length", &self.length)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NiceOutputMessage {
|
||||
pub buffers: *mut gio::ffi::GOutputVector,
|
||||
pub n_buffers: c_int,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for NiceOutputMessage {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct(&format!("NiceOutputMessage @ {:p}", self))
|
||||
.field("buffers", &self.buffers)
|
||||
.field("n_buffers", &self.n_buffers)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PseudoTcpCallbacks {
|
||||
pub user_data: gpointer,
|
||||
pub PseudoTcpOpened: Option<unsafe extern "C" fn(*mut PseudoTcpSocket, gpointer)>,
|
||||
pub PseudoTcpReadable: Option<unsafe extern "C" fn(*mut PseudoTcpSocket, gpointer)>,
|
||||
pub PseudoTcpWritable: Option<unsafe extern "C" fn(*mut PseudoTcpSocket, gpointer)>,
|
||||
pub PseudoTcpClosed: Option<unsafe extern "C" fn(*mut PseudoTcpSocket, u32, gpointer)>,
|
||||
pub WritePacket: Option<
|
||||
unsafe extern "C" fn(
|
||||
*mut PseudoTcpSocket,
|
||||
*const c_char,
|
||||
u32,
|
||||
gpointer,
|
||||
) -> PseudoTcpWriteResult,
|
||||
>,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for PseudoTcpCallbacks {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct(&format!("PseudoTcpCallbacks @ {:p}", self))
|
||||
.field("user_data", &self.user_data)
|
||||
.field("PseudoTcpOpened", &self.PseudoTcpOpened)
|
||||
.field("PseudoTcpReadable", &self.PseudoTcpReadable)
|
||||
.field("PseudoTcpWritable", &self.PseudoTcpWritable)
|
||||
.field("PseudoTcpClosed", &self.PseudoTcpClosed)
|
||||
.field("WritePacket", &self.WritePacket)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct _PseudoTcpSocketClass(c_void);
|
||||
|
||||
pub type PseudoTcpSocketClass = *mut _PseudoTcpSocketClass;
|
||||
|
||||
// Classes
|
||||
#[repr(C)]
|
||||
pub struct NiceAgent(c_void);
|
||||
|
||||
impl ::std::fmt::Debug for NiceAgent {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct(&format!("NiceAgent @ {:p}", self)).finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PseudoTcpSocket(c_void);
|
||||
|
||||
impl ::std::fmt::Debug for PseudoTcpSocket {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct(&format!("PseudoTcpSocket @ {:p}", self))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "nice")]
|
||||
extern "C" {
|
||||
|
||||
//=========================================================================
|
||||
// NiceAddress
|
||||
//=========================================================================
|
||||
pub fn nice_address_copy_to_sockaddr(addr: *const NiceAddress, sin: *mut sockaddr);
|
||||
pub fn nice_address_dup(addr: *const NiceAddress) -> *mut NiceAddress;
|
||||
pub fn nice_address_equal(a: *const NiceAddress, b: *const NiceAddress) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_8", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_8")))]
|
||||
pub fn nice_address_equal_no_port(a: *const NiceAddress, b: *const NiceAddress) -> gboolean;
|
||||
pub fn nice_address_free(addr: *mut NiceAddress);
|
||||
pub fn nice_address_get_port(addr: *const NiceAddress) -> c_uint;
|
||||
pub fn nice_address_init(addr: *mut NiceAddress);
|
||||
pub fn nice_address_ip_version(addr: *const NiceAddress) -> c_int;
|
||||
pub fn nice_address_is_private(addr: *const NiceAddress) -> gboolean;
|
||||
pub fn nice_address_is_valid(addr: *const NiceAddress) -> gboolean;
|
||||
pub fn nice_address_set_from_sockaddr(addr: *mut NiceAddress, sin: *const sockaddr);
|
||||
pub fn nice_address_set_from_string(addr: *mut NiceAddress, str: *const c_char) -> gboolean;
|
||||
pub fn nice_address_set_ipv4(addr: *mut NiceAddress, addr_ipv4: u32);
|
||||
pub fn nice_address_set_ipv6(addr: *mut NiceAddress, addr_ipv6: *const u8);
|
||||
pub fn nice_address_set_port(addr: *mut NiceAddress, port: c_uint);
|
||||
pub fn nice_address_to_string(addr: *const NiceAddress, dst: *mut c_char);
|
||||
pub fn nice_address_new() -> *mut NiceAddress;
|
||||
|
||||
//=========================================================================
|
||||
// NiceCandidate
|
||||
//=========================================================================
|
||||
pub fn nice_candidate_get_type() -> GType;
|
||||
pub fn nice_candidate_new(type_: NiceCandidateType) -> *mut NiceCandidate;
|
||||
pub fn nice_candidate_copy(candidate: *const NiceCandidate) -> *mut NiceCandidate;
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
pub fn nice_candidate_equal_target(
|
||||
candidate1: *const NiceCandidate,
|
||||
candidate2: *const NiceCandidate,
|
||||
) -> gboolean;
|
||||
pub fn nice_candidate_free(candidate: *mut NiceCandidate);
|
||||
#[cfg(any(feature = "v0_1_18", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_18")))]
|
||||
pub fn nice_candidate_transport_to_string(transport: NiceCandidateTransport) -> *const c_char;
|
||||
#[cfg(any(feature = "v0_1_18", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_18")))]
|
||||
pub fn nice_candidate_type_to_string(type_: NiceCandidateType) -> *const c_char;
|
||||
|
||||
//=========================================================================
|
||||
// NiceAgent
|
||||
//=========================================================================
|
||||
pub fn nice_agent_get_type() -> GType;
|
||||
pub fn nice_agent_new(
|
||||
ctx: *mut glib::ffi::GMainContext,
|
||||
compat: NiceCompatibility,
|
||||
) -> *mut NiceAgent;
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
pub fn nice_agent_new_full(
|
||||
ctx: *mut glib::ffi::GMainContext,
|
||||
compat: NiceCompatibility,
|
||||
flags: NiceAgentOption,
|
||||
) -> *mut NiceAgent;
|
||||
pub fn nice_agent_new_reliable(
|
||||
ctx: *mut glib::ffi::GMainContext,
|
||||
compat: NiceCompatibility,
|
||||
) -> *mut NiceAgent;
|
||||
pub fn nice_agent_add_local_address(agent: *mut NiceAgent, addr: *mut NiceAddress) -> gboolean;
|
||||
pub fn nice_agent_add_stream(agent: *mut NiceAgent, n_components: c_uint) -> c_uint;
|
||||
pub fn nice_agent_attach_recv(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
ctx: *mut glib::ffi::GMainContext,
|
||||
func: NiceAgentRecvFunc,
|
||||
data: gpointer,
|
||||
) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_16", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_16")))]
|
||||
pub fn nice_agent_close_async(
|
||||
agent: *mut NiceAgent,
|
||||
callback: gio::ffi::GAsyncReadyCallback,
|
||||
callback_data: gpointer,
|
||||
);
|
||||
#[cfg(any(feature = "v0_1_20", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_20")))]
|
||||
pub fn nice_agent_consent_lost(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_6", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_6")))]
|
||||
pub fn nice_agent_forget_relays(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
) -> gboolean;
|
||||
pub fn nice_agent_gather_candidates(agent: *mut NiceAgent, stream_id: c_uint) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_4", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_4")))]
|
||||
pub fn nice_agent_generate_local_candidate_sdp(
|
||||
agent: *mut NiceAgent,
|
||||
candidate: *mut NiceCandidate,
|
||||
) -> *mut c_char;
|
||||
#[cfg(any(feature = "v0_1_4", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_4")))]
|
||||
pub fn nice_agent_generate_local_sdp(agent: *mut NiceAgent) -> *mut c_char;
|
||||
#[cfg(any(feature = "v0_1_4", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_4")))]
|
||||
pub fn nice_agent_generate_local_stream_sdp(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
include_non_ice: gboolean,
|
||||
) -> *mut c_char;
|
||||
#[cfg(any(feature = "v0_1_8", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_8")))]
|
||||
pub fn nice_agent_get_component_state(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
) -> NiceComponentState;
|
||||
pub fn nice_agent_get_default_local_candidate(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
) -> *mut NiceCandidate;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn nice_agent_get_io_stream(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
) -> *mut gio::ffi::GIOStream;
|
||||
pub fn nice_agent_get_local_candidates(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
) -> *mut glib::ffi::GSList;
|
||||
pub fn nice_agent_get_local_credentials(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
ufrag: *mut *mut c_char,
|
||||
pwd: *mut *mut c_char,
|
||||
) -> gboolean;
|
||||
pub fn nice_agent_get_remote_candidates(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
) -> *mut glib::ffi::GSList;
|
||||
pub fn nice_agent_get_selected_pair(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
local: *mut *mut NiceCandidate,
|
||||
remote: *mut *mut NiceCandidate,
|
||||
) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn nice_agent_get_selected_socket(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
) -> *mut gio::ffi::GSocket;
|
||||
#[cfg(any(feature = "v0_1_17", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_17")))]
|
||||
pub fn nice_agent_get_sockets(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
) -> *mut glib::ffi::GPtrArray;
|
||||
#[cfg(any(feature = "v0_1_4", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_4")))]
|
||||
pub fn nice_agent_get_stream_name(agent: *mut NiceAgent, stream_id: c_uint) -> *const c_char;
|
||||
#[cfg(any(feature = "v0_1_4", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_4")))]
|
||||
pub fn nice_agent_parse_remote_candidate_sdp(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
sdp: *const c_char,
|
||||
) -> *mut NiceCandidate;
|
||||
#[cfg(any(feature = "v0_1_4", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_4")))]
|
||||
pub fn nice_agent_parse_remote_sdp(agent: *mut NiceAgent, sdp: *const c_char) -> c_int;
|
||||
#[cfg(any(feature = "v0_1_4", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_4")))]
|
||||
pub fn nice_agent_parse_remote_stream_sdp(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
sdp: *const c_char,
|
||||
ufrag: *mut *mut c_char,
|
||||
pwd: *mut *mut c_char,
|
||||
) -> *mut glib::ffi::GSList;
|
||||
#[cfg(any(feature = "v0_1_16", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_16")))]
|
||||
pub fn nice_agent_peer_candidate_gathering_done(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn nice_agent_recv(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
buf: *mut u8,
|
||||
buf_len: size_t,
|
||||
cancellable: *mut gio::ffi::GCancellable,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> ssize_t;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn nice_agent_recv_messages(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
messages: *mut NiceInputMessage,
|
||||
n_messages: c_uint,
|
||||
cancellable: *mut gio::ffi::GCancellable,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> c_int;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn nice_agent_recv_messages_nonblocking(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
messages: *mut NiceInputMessage,
|
||||
n_messages: c_uint,
|
||||
cancellable: *mut gio::ffi::GCancellable,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> c_int;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn nice_agent_recv_nonblocking(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
buf: *mut u8,
|
||||
buf_len: size_t,
|
||||
cancellable: *mut gio::ffi::GCancellable,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> ssize_t;
|
||||
pub fn nice_agent_remove_stream(agent: *mut NiceAgent, stream_id: c_uint);
|
||||
pub fn nice_agent_restart(agent: *mut NiceAgent) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_6", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_6")))]
|
||||
pub fn nice_agent_restart_stream(agent: *mut NiceAgent, stream_id: c_uint) -> gboolean;
|
||||
pub fn nice_agent_send(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
len: c_uint,
|
||||
buf: *const c_char,
|
||||
) -> c_int;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn nice_agent_send_messages_nonblocking(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
messages: *const NiceOutputMessage,
|
||||
n_messages: c_uint,
|
||||
cancellable: *mut gio::ffi::GCancellable,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> c_int;
|
||||
pub fn nice_agent_set_local_credentials(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
ufrag: *const c_char,
|
||||
pwd: *const c_char,
|
||||
) -> gboolean;
|
||||
pub fn nice_agent_set_port_range(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
min_port: c_uint,
|
||||
max_port: c_uint,
|
||||
);
|
||||
pub fn nice_agent_set_relay_info(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
server_ip: *const c_char,
|
||||
server_port: c_uint,
|
||||
username: *const c_char,
|
||||
password: *const c_char,
|
||||
type_: NiceRelayType,
|
||||
) -> gboolean;
|
||||
pub fn nice_agent_set_remote_candidates(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
candidates: *const glib::ffi::GSList,
|
||||
) -> c_int;
|
||||
pub fn nice_agent_set_remote_credentials(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
ufrag: *const c_char,
|
||||
pwd: *const c_char,
|
||||
) -> gboolean;
|
||||
pub fn nice_agent_set_selected_pair(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
lfoundation: *const c_char,
|
||||
rfoundation: *const c_char,
|
||||
) -> gboolean;
|
||||
pub fn nice_agent_set_selected_remote_candidate(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
component_id: c_uint,
|
||||
candidate: *mut NiceCandidate,
|
||||
) -> gboolean;
|
||||
pub fn nice_agent_set_software(agent: *mut NiceAgent, software: *const c_char);
|
||||
#[cfg(any(feature = "v0_1_4", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_4")))]
|
||||
pub fn nice_agent_set_stream_name(
|
||||
agent: *mut NiceAgent,
|
||||
stream_id: c_uint,
|
||||
name: *const c_char,
|
||||
) -> gboolean;
|
||||
pub fn nice_agent_set_stream_tos(agent: *mut NiceAgent, stream_id: c_uint, tos: c_int);
|
||||
|
||||
//=========================================================================
|
||||
// PseudoTcpSocket
|
||||
//=========================================================================
|
||||
pub fn pseudo_tcp_socket_get_type() -> GType;
|
||||
pub fn pseudo_tcp_socket_new(
|
||||
conversation: u32,
|
||||
callbacks: *mut PseudoTcpCallbacks,
|
||||
) -> *mut PseudoTcpSocket;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn pseudo_tcp_socket_can_send(self_: *mut PseudoTcpSocket) -> gboolean;
|
||||
pub fn pseudo_tcp_socket_close(self_: *mut PseudoTcpSocket, force: gboolean);
|
||||
pub fn pseudo_tcp_socket_connect(self_: *mut PseudoTcpSocket) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn pseudo_tcp_socket_get_available_bytes(self_: *mut PseudoTcpSocket) -> c_int;
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn pseudo_tcp_socket_get_available_send_space(self_: *mut PseudoTcpSocket) -> size_t;
|
||||
pub fn pseudo_tcp_socket_get_error(self_: *mut PseudoTcpSocket) -> c_int;
|
||||
pub fn pseudo_tcp_socket_get_next_clock(
|
||||
self_: *mut PseudoTcpSocket,
|
||||
timeout: *mut u64,
|
||||
) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_8", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_8")))]
|
||||
pub fn pseudo_tcp_socket_is_closed(self_: *mut PseudoTcpSocket) -> gboolean;
|
||||
#[cfg(any(feature = "v0_1_8", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_8")))]
|
||||
pub fn pseudo_tcp_socket_is_closed_remotely(self_: *mut PseudoTcpSocket) -> gboolean;
|
||||
pub fn pseudo_tcp_socket_notify_clock(self_: *mut PseudoTcpSocket);
|
||||
#[cfg(any(feature = "v0_1_5", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_5")))]
|
||||
pub fn pseudo_tcp_socket_notify_message(
|
||||
self_: *mut PseudoTcpSocket,
|
||||
message: *mut NiceInputMessage,
|
||||
) -> gboolean;
|
||||
pub fn pseudo_tcp_socket_notify_mtu(self_: *mut PseudoTcpSocket, mtu: u16);
|
||||
pub fn pseudo_tcp_socket_notify_packet(
|
||||
self_: *mut PseudoTcpSocket,
|
||||
buffer: *const c_char,
|
||||
len: u32,
|
||||
) -> gboolean;
|
||||
pub fn pseudo_tcp_socket_recv(
|
||||
self_: *mut PseudoTcpSocket,
|
||||
buffer: *mut c_char,
|
||||
len: size_t,
|
||||
) -> c_int;
|
||||
pub fn pseudo_tcp_socket_send(
|
||||
self_: *mut PseudoTcpSocket,
|
||||
buffer: *const c_char,
|
||||
len: u32,
|
||||
) -> c_int;
|
||||
#[cfg(any(feature = "v0_1_8", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_8")))]
|
||||
pub fn pseudo_tcp_socket_set_time(self_: *mut PseudoTcpSocket, current_time: u32);
|
||||
#[cfg(any(feature = "v0_1_8", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_8")))]
|
||||
pub fn pseudo_tcp_socket_shutdown(self_: *mut PseudoTcpSocket, how: PseudoTcpShutdown);
|
||||
|
||||
//=========================================================================
|
||||
// Other functions
|
||||
//=========================================================================
|
||||
#[cfg(any(feature = "v0_1_6", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_6")))]
|
||||
pub fn nice_component_state_to_string(state: NiceComponentState) -> *const c_char;
|
||||
pub fn nice_debug_disable(with_stun: gboolean);
|
||||
pub fn nice_debug_enable(with_stun: gboolean);
|
||||
pub fn nice_interfaces_get_ip_for_interface(interface_name: *mut c_char) -> *mut c_char;
|
||||
pub fn nice_interfaces_get_local_interfaces() -> *mut glib::ffi::GList;
|
||||
pub fn nice_interfaces_get_local_ips(include_loopback: gboolean) -> *mut glib::ffi::GList;
|
||||
pub fn pseudo_tcp_set_debug_level(level: PseudoTcpDebugLevel);
|
||||
|
||||
}
|
|
@ -0,0 +1,418 @@
|
|||
// Generated by gir (https://github.com/gtk-rs/gir @ 5bbf6cb)
|
||||
// from ../../gir-files (@ 8e47c67)
|
||||
// DO NOT EDIT
|
||||
|
||||
use std::{
|
||||
env,
|
||||
error::Error,
|
||||
ffi::OsString,
|
||||
mem::{align_of, size_of},
|
||||
path::Path,
|
||||
process::Command,
|
||||
str,
|
||||
};
|
||||
|
||||
use nice_sys::*;
|
||||
use tempfile::Builder;
|
||||
|
||||
static PACKAGES: &[&str] = &["nice"];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Compiler {
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||
let mut args = get_var("CC", "cc")?;
|
||||
args.push("-Wno-deprecated-declarations".to_owned());
|
||||
// For _Generic
|
||||
args.push("-std=c11".to_owned());
|
||||
// For %z support in printf when using MinGW.
|
||||
args.push("-D__USE_MINGW_ANSI_STDIO".to_owned());
|
||||
args.extend(get_var("CFLAGS", "")?);
|
||||
args.extend(get_var("CPPFLAGS", "")?);
|
||||
args.extend(pkg_config_cflags(PACKAGES)?);
|
||||
Ok(Self { args })
|
||||
}
|
||||
|
||||
pub fn compile(&self, src: &Path, out: &Path) -> Result<(), Box<dyn Error>> {
|
||||
let mut cmd = self.to_command();
|
||||
cmd.arg(src);
|
||||
cmd.arg("-o");
|
||||
cmd.arg(out);
|
||||
let status = cmd.spawn()?.wait()?;
|
||||
if !status.success() {
|
||||
return Err(format!("compilation command {:?} failed, {}", &cmd, status).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_command(&self) -> Command {
|
||||
let mut cmd = Command::new(&self.args[0]);
|
||||
cmd.args(&self.args[1..]);
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
fn get_var(name: &str, default: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
match env::var(name) {
|
||||
Ok(value) => Ok(shell_words::split(&value)?),
|
||||
Err(env::VarError::NotPresent) => Ok(shell_words::split(default)?),
|
||||
Err(err) => Err(format!("{} {}", name, err).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn pkg_config_cflags(packages: &[&str]) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
if packages.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let pkg_config = env::var_os("PKG_CONFIG").unwrap_or_else(|| OsString::from("pkg-config"));
|
||||
let mut cmd = Command::new(pkg_config);
|
||||
cmd.arg("--cflags");
|
||||
cmd.args(packages);
|
||||
let out = cmd.output()?;
|
||||
if !out.status.success() {
|
||||
return Err(format!("command {:?} returned {}", &cmd, out.status).into());
|
||||
}
|
||||
let stdout = str::from_utf8(&out.stdout)?;
|
||||
Ok(shell_words::split(stdout.trim())?)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
struct Layout {
|
||||
size: usize,
|
||||
alignment: usize,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||
struct Results {
|
||||
/// Number of successfully completed tests.
|
||||
passed: usize,
|
||||
/// Total number of failed tests (including those that failed to compile).
|
||||
failed: usize,
|
||||
}
|
||||
|
||||
impl Results {
|
||||
fn record_passed(&mut self) {
|
||||
self.passed += 1;
|
||||
}
|
||||
|
||||
fn record_failed(&mut self) {
|
||||
self.failed += 1;
|
||||
}
|
||||
|
||||
fn summary(&self) -> String {
|
||||
format!("{} passed; {} failed", self.passed, self.failed)
|
||||
}
|
||||
|
||||
fn expect_total_success(&self) {
|
||||
if self.failed == 0 {
|
||||
println!("OK: {}", self.summary());
|
||||
}
|
||||
else {
|
||||
panic!("FAILED: {}", self.summary());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_validate_constants_with_c() {
|
||||
let mut c_constants: Vec<(String, String)> = Vec::new();
|
||||
|
||||
for l in get_c_output("constant").unwrap().lines() {
|
||||
let mut words = l.trim().split(';');
|
||||
let name = words.next().expect("Failed to parse name").to_owned();
|
||||
let value = words
|
||||
.next()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.expect("Failed to parse value");
|
||||
c_constants.push((name, value));
|
||||
}
|
||||
|
||||
let mut results = Results::default();
|
||||
|
||||
for ((rust_name, rust_value), (c_name, c_value)) in RUST_CONSTANTS.iter().zip(c_constants.iter())
|
||||
{
|
||||
if rust_name != c_name {
|
||||
results.record_failed();
|
||||
eprintln!("Name mismatch:\nRust: {:?}\nC: {:?}", rust_name, c_name,);
|
||||
continue;
|
||||
}
|
||||
|
||||
if rust_value != c_value {
|
||||
results.record_failed();
|
||||
eprintln!(
|
||||
"Constant value mismatch for {}\nRust: {:?}\nC: {:?}",
|
||||
rust_name, rust_value, &c_value
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
results.record_passed();
|
||||
}
|
||||
|
||||
results.expect_total_success();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_validate_layout_with_c() {
|
||||
let mut c_layouts = Vec::new();
|
||||
|
||||
for l in get_c_output("layout").unwrap().lines() {
|
||||
let mut words = l.trim().split(';');
|
||||
let name = words.next().expect("Failed to parse name").to_owned();
|
||||
let size = words
|
||||
.next()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.expect("Failed to parse size");
|
||||
let alignment = words
|
||||
.next()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.expect("Failed to parse alignment");
|
||||
c_layouts.push((name, Layout { size, alignment }));
|
||||
}
|
||||
|
||||
let mut results = Results::default();
|
||||
|
||||
for ((rust_name, rust_layout), (c_name, c_layout)) in RUST_LAYOUTS.iter().zip(c_layouts.iter()) {
|
||||
if rust_name != c_name {
|
||||
results.record_failed();
|
||||
eprintln!("Name mismatch:\nRust: {:?}\nC: {:?}", rust_name, c_name,);
|
||||
continue;
|
||||
}
|
||||
|
||||
if rust_layout != c_layout {
|
||||
results.record_failed();
|
||||
eprintln!(
|
||||
"Layout mismatch for {}\nRust: {:?}\nC: {:?}",
|
||||
rust_name, rust_layout, &c_layout
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
results.record_passed();
|
||||
}
|
||||
|
||||
results.expect_total_success();
|
||||
}
|
||||
|
||||
fn get_c_output(name: &str) -> Result<String, Box<dyn Error>> {
|
||||
let tmpdir = Builder::new().prefix("abi").tempdir()?;
|
||||
let exe = tmpdir.path().join(name);
|
||||
let c_file = Path::new("tests").join(name).with_extension("c");
|
||||
|
||||
let cc = Compiler::new().expect("configured compiler");
|
||||
cc.compile(&c_file, &exe)?;
|
||||
|
||||
let mut abi_cmd = Command::new(exe);
|
||||
let output = abi_cmd.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(format!("command {:?} failed, {:?}", &abi_cmd, &output).into());
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(output.stdout)?)
|
||||
}
|
||||
|
||||
const RUST_LAYOUTS: &[(&str, Layout)] = &[
|
||||
(
|
||||
"NiceAddress",
|
||||
Layout {
|
||||
size: size_of::<NiceAddress>(),
|
||||
alignment: align_of::<NiceAddress>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceAgentClass",
|
||||
Layout {
|
||||
size: size_of::<NiceAgentClass>(),
|
||||
alignment: align_of::<NiceAgentClass>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceAgentOption",
|
||||
Layout {
|
||||
size: size_of::<NiceAgentOption>(),
|
||||
alignment: align_of::<NiceAgentOption>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceCandidate",
|
||||
Layout {
|
||||
size: size_of::<NiceCandidate>(),
|
||||
alignment: align_of::<NiceCandidate>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceCandidateTransport",
|
||||
Layout {
|
||||
size: size_of::<NiceCandidateTransport>(),
|
||||
alignment: align_of::<NiceCandidateTransport>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceCandidateType",
|
||||
Layout {
|
||||
size: size_of::<NiceCandidateType>(),
|
||||
alignment: align_of::<NiceCandidateType>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceCompatibility",
|
||||
Layout {
|
||||
size: size_of::<NiceCompatibility>(),
|
||||
alignment: align_of::<NiceCompatibility>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceComponentState",
|
||||
Layout {
|
||||
size: size_of::<NiceComponentState>(),
|
||||
alignment: align_of::<NiceComponentState>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceComponentType",
|
||||
Layout {
|
||||
size: size_of::<NiceComponentType>(),
|
||||
alignment: align_of::<NiceComponentType>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceInputMessage",
|
||||
Layout {
|
||||
size: size_of::<NiceInputMessage>(),
|
||||
alignment: align_of::<NiceInputMessage>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceNominationMode",
|
||||
Layout {
|
||||
size: size_of::<NiceNominationMode>(),
|
||||
alignment: align_of::<NiceNominationMode>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceOutputMessage",
|
||||
Layout {
|
||||
size: size_of::<NiceOutputMessage>(),
|
||||
alignment: align_of::<NiceOutputMessage>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceProxyType",
|
||||
Layout {
|
||||
size: size_of::<NiceProxyType>(),
|
||||
alignment: align_of::<NiceProxyType>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"NiceRelayType",
|
||||
Layout {
|
||||
size: size_of::<NiceRelayType>(),
|
||||
alignment: align_of::<NiceRelayType>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"PseudoTcpCallbacks",
|
||||
Layout {
|
||||
size: size_of::<PseudoTcpCallbacks>(),
|
||||
alignment: align_of::<PseudoTcpCallbacks>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"PseudoTcpDebugLevel",
|
||||
Layout {
|
||||
size: size_of::<PseudoTcpDebugLevel>(),
|
||||
alignment: align_of::<PseudoTcpDebugLevel>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"PseudoTcpShutdown",
|
||||
Layout {
|
||||
size: size_of::<PseudoTcpShutdown>(),
|
||||
alignment: align_of::<PseudoTcpShutdown>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"PseudoTcpState",
|
||||
Layout {
|
||||
size: size_of::<PseudoTcpState>(),
|
||||
alignment: align_of::<PseudoTcpState>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"PseudoTcpWriteResult",
|
||||
Layout {
|
||||
size: size_of::<PseudoTcpWriteResult>(),
|
||||
alignment: align_of::<PseudoTcpWriteResult>(),
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
const RUST_CONSTANTS: &[(&str, &str)] = &[
|
||||
("NICE_AGENT_MAX_REMOTE_CANDIDATES", "25"),
|
||||
("(guint) NICE_AGENT_OPTION_CONSENT_FRESHNESS", "32"),
|
||||
("(guint) NICE_AGENT_OPTION_ICE_TRICKLE", "8"),
|
||||
("(guint) NICE_AGENT_OPTION_LITE_MODE", "4"),
|
||||
("(guint) NICE_AGENT_OPTION_REGULAR_NOMINATION", "1"),
|
||||
("(guint) NICE_AGENT_OPTION_RELIABLE", "2"),
|
||||
("(guint) NICE_AGENT_OPTION_SUPPORT_RENOMINATION", "16"),
|
||||
("NICE_CANDIDATE_MAX_FOUNDATION", "33"),
|
||||
("NICE_CANDIDATE_MAX_LOCAL_ADDRESSES", "64"),
|
||||
("NICE_CANDIDATE_MAX_TURN_SERVERS", "8"),
|
||||
("(gint) NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE", "1"),
|
||||
("(gint) NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE", "2"),
|
||||
("(gint) NICE_CANDIDATE_TRANSPORT_TCP_SO", "3"),
|
||||
("(gint) NICE_CANDIDATE_TRANSPORT_UDP", "0"),
|
||||
("(gint) NICE_CANDIDATE_TYPE_HOST", "0"),
|
||||
("(gint) NICE_CANDIDATE_TYPE_PEER_REFLEXIVE", "2"),
|
||||
("(gint) NICE_CANDIDATE_TYPE_RELAYED", "3"),
|
||||
("(gint) NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE", "1"),
|
||||
("(gint) NICE_COMPATIBILITY_DRAFT19", "0"),
|
||||
("(gint) NICE_COMPATIBILITY_GOOGLE", "1"),
|
||||
("(gint) NICE_COMPATIBILITY_LAST", "5"),
|
||||
("(gint) NICE_COMPATIBILITY_MSN", "2"),
|
||||
("(gint) NICE_COMPATIBILITY_OC2007", "4"),
|
||||
("(gint) NICE_COMPATIBILITY_OC2007R2", "5"),
|
||||
("(gint) NICE_COMPATIBILITY_RFC5245", "0"),
|
||||
("(gint) NICE_COMPATIBILITY_WLM2009", "3"),
|
||||
("(gint) NICE_COMPONENT_STATE_CONNECTED", "3"),
|
||||
("(gint) NICE_COMPONENT_STATE_CONNECTING", "2"),
|
||||
("(gint) NICE_COMPONENT_STATE_DISCONNECTED", "0"),
|
||||
("(gint) NICE_COMPONENT_STATE_FAILED", "5"),
|
||||
("(gint) NICE_COMPONENT_STATE_GATHERING", "1"),
|
||||
("(gint) NICE_COMPONENT_STATE_LAST", "6"),
|
||||
("(gint) NICE_COMPONENT_STATE_READY", "4"),
|
||||
("(gint) NICE_COMPONENT_TYPE_RTCP", "2"),
|
||||
("(gint) NICE_COMPONENT_TYPE_RTP", "1"),
|
||||
("(gint) NICE_NOMINATION_MODE_AGGRESSIVE", "1"),
|
||||
("(gint) NICE_NOMINATION_MODE_REGULAR", "0"),
|
||||
("(gint) NICE_PROXY_TYPE_HTTP", "2"),
|
||||
("(gint) NICE_PROXY_TYPE_LAST", "2"),
|
||||
("(gint) NICE_PROXY_TYPE_NONE", "0"),
|
||||
("(gint) NICE_PROXY_TYPE_SOCKS5", "1"),
|
||||
("(gint) NICE_RELAY_TYPE_TURN_TCP", "1"),
|
||||
("(gint) NICE_RELAY_TYPE_TURN_TLS", "2"),
|
||||
("(gint) NICE_RELAY_TYPE_TURN_UDP", "0"),
|
||||
("(gint) PSEUDO_TCP_CLOSED", "4"),
|
||||
("(gint) PSEUDO_TCP_CLOSE_WAIT", "9"),
|
||||
("(gint) PSEUDO_TCP_CLOSING", "7"),
|
||||
("(gint) PSEUDO_TCP_DEBUG_NONE", "0"),
|
||||
("(gint) PSEUDO_TCP_DEBUG_NORMAL", "1"),
|
||||
("(gint) PSEUDO_TCP_DEBUG_VERBOSE", "2"),
|
||||
("(gint) PSEUDO_TCP_ESTABLISHED", "3"),
|
||||
("(gint) PSEUDO_TCP_FIN_WAIT_1", "5"),
|
||||
("(gint) PSEUDO_TCP_FIN_WAIT_2", "6"),
|
||||
("(gint) PSEUDO_TCP_LAST_ACK", "10"),
|
||||
("(gint) PSEUDO_TCP_LISTEN", "0"),
|
||||
("(gint) PSEUDO_TCP_SHUTDOWN_RD", "0"),
|
||||
("(gint) PSEUDO_TCP_SHUTDOWN_RDWR", "2"),
|
||||
("(gint) PSEUDO_TCP_SHUTDOWN_WR", "1"),
|
||||
("(gint) PSEUDO_TCP_SYN_RECEIVED", "2"),
|
||||
("(gint) PSEUDO_TCP_SYN_SENT", "1"),
|
||||
("(gint) PSEUDO_TCP_TIME_WAIT", "8"),
|
||||
("(gint) WR_FAIL", "2"),
|
||||
("(gint) WR_SUCCESS", "0"),
|
||||
("(gint) WR_TOO_LARGE", "1"),
|
||||
];
|
|
@ -0,0 +1,96 @@
|
|||
// Generated by gir (https://github.com/gtk-rs/gir @ 5bbf6cb)
|
||||
// from ../../gir-files (@ 8e47c67)
|
||||
// DO NOT EDIT
|
||||
|
||||
#include "manual.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#define PRINT_CONSTANT(CONSTANT_NAME) \
|
||||
printf("%s;", #CONSTANT_NAME); \
|
||||
printf(_Generic((CONSTANT_NAME), \
|
||||
char *: "%s", \
|
||||
const char *: "%s", \
|
||||
char: "%c", \
|
||||
signed char: "%hhd", \
|
||||
unsigned char: "%hhu", \
|
||||
short int: "%hd", \
|
||||
unsigned short int: "%hu", \
|
||||
int: "%d", \
|
||||
unsigned int: "%u", \
|
||||
long: "%ld", \
|
||||
unsigned long: "%lu", \
|
||||
long long: "%lld", \
|
||||
unsigned long long: "%llu", \
|
||||
float: "%f", \
|
||||
double: "%f", \
|
||||
long double: "%ld"), \
|
||||
CONSTANT_NAME); \
|
||||
printf("\n");
|
||||
|
||||
int main() {
|
||||
PRINT_CONSTANT(NICE_AGENT_MAX_REMOTE_CANDIDATES);
|
||||
PRINT_CONSTANT((guint) NICE_AGENT_OPTION_CONSENT_FRESHNESS);
|
||||
PRINT_CONSTANT((guint) NICE_AGENT_OPTION_ICE_TRICKLE);
|
||||
PRINT_CONSTANT((guint) NICE_AGENT_OPTION_LITE_MODE);
|
||||
PRINT_CONSTANT((guint) NICE_AGENT_OPTION_REGULAR_NOMINATION);
|
||||
PRINT_CONSTANT((guint) NICE_AGENT_OPTION_RELIABLE);
|
||||
PRINT_CONSTANT((guint) NICE_AGENT_OPTION_SUPPORT_RENOMINATION);
|
||||
PRINT_CONSTANT(NICE_CANDIDATE_MAX_FOUNDATION);
|
||||
PRINT_CONSTANT(NICE_CANDIDATE_MAX_LOCAL_ADDRESSES);
|
||||
PRINT_CONSTANT(NICE_CANDIDATE_MAX_TURN_SERVERS);
|
||||
PRINT_CONSTANT((gint) NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE);
|
||||
PRINT_CONSTANT((gint) NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE);
|
||||
PRINT_CONSTANT((gint) NICE_CANDIDATE_TRANSPORT_TCP_SO);
|
||||
PRINT_CONSTANT((gint) NICE_CANDIDATE_TRANSPORT_UDP);
|
||||
PRINT_CONSTANT((gint) NICE_CANDIDATE_TYPE_HOST);
|
||||
PRINT_CONSTANT((gint) NICE_CANDIDATE_TYPE_PEER_REFLEXIVE);
|
||||
PRINT_CONSTANT((gint) NICE_CANDIDATE_TYPE_RELAYED);
|
||||
PRINT_CONSTANT((gint) NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE);
|
||||
PRINT_CONSTANT((gint) NICE_COMPATIBILITY_DRAFT19);
|
||||
PRINT_CONSTANT((gint) NICE_COMPATIBILITY_GOOGLE);
|
||||
PRINT_CONSTANT((gint) NICE_COMPATIBILITY_LAST);
|
||||
PRINT_CONSTANT((gint) NICE_COMPATIBILITY_MSN);
|
||||
PRINT_CONSTANT((gint) NICE_COMPATIBILITY_OC2007);
|
||||
PRINT_CONSTANT((gint) NICE_COMPATIBILITY_OC2007R2);
|
||||
PRINT_CONSTANT((gint) NICE_COMPATIBILITY_RFC5245);
|
||||
PRINT_CONSTANT((gint) NICE_COMPATIBILITY_WLM2009);
|
||||
PRINT_CONSTANT((gint) NICE_COMPONENT_STATE_CONNECTED);
|
||||
PRINT_CONSTANT((gint) NICE_COMPONENT_STATE_CONNECTING);
|
||||
PRINT_CONSTANT((gint) NICE_COMPONENT_STATE_DISCONNECTED);
|
||||
PRINT_CONSTANT((gint) NICE_COMPONENT_STATE_FAILED);
|
||||
PRINT_CONSTANT((gint) NICE_COMPONENT_STATE_GATHERING);
|
||||
PRINT_CONSTANT((gint) NICE_COMPONENT_STATE_LAST);
|
||||
PRINT_CONSTANT((gint) NICE_COMPONENT_STATE_READY);
|
||||
PRINT_CONSTANT((gint) NICE_COMPONENT_TYPE_RTCP);
|
||||
PRINT_CONSTANT((gint) NICE_COMPONENT_TYPE_RTP);
|
||||
PRINT_CONSTANT((gint) NICE_NOMINATION_MODE_AGGRESSIVE);
|
||||
PRINT_CONSTANT((gint) NICE_NOMINATION_MODE_REGULAR);
|
||||
PRINT_CONSTANT((gint) NICE_PROXY_TYPE_HTTP);
|
||||
PRINT_CONSTANT((gint) NICE_PROXY_TYPE_LAST);
|
||||
PRINT_CONSTANT((gint) NICE_PROXY_TYPE_NONE);
|
||||
PRINT_CONSTANT((gint) NICE_PROXY_TYPE_SOCKS5);
|
||||
PRINT_CONSTANT((gint) NICE_RELAY_TYPE_TURN_TCP);
|
||||
PRINT_CONSTANT((gint) NICE_RELAY_TYPE_TURN_TLS);
|
||||
PRINT_CONSTANT((gint) NICE_RELAY_TYPE_TURN_UDP);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_CLOSED);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_CLOSE_WAIT);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_CLOSING);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_DEBUG_NONE);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_DEBUG_NORMAL);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_DEBUG_VERBOSE);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_ESTABLISHED);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_FIN_WAIT_1);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_FIN_WAIT_2);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_LAST_ACK);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_LISTEN);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_SHUTDOWN_RD);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_SHUTDOWN_RDWR);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_SHUTDOWN_WR);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_SYN_RECEIVED);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_SYN_SENT);
|
||||
PRINT_CONSTANT((gint) PSEUDO_TCP_TIME_WAIT);
|
||||
PRINT_CONSTANT((gint) WR_FAIL);
|
||||
PRINT_CONSTANT((gint) WR_SUCCESS);
|
||||
PRINT_CONSTANT((gint) WR_TOO_LARGE);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Generated by gir (https://github.com/gtk-rs/gir @ 5bbf6cb)
|
||||
// from ../../gir-files (@ 8e47c67)
|
||||
// DO NOT EDIT
|
||||
|
||||
#include "manual.h"
|
||||
#include <stdalign.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("%s;%zu;%zu\n", "NiceAddress", sizeof(NiceAddress), alignof(NiceAddress));
|
||||
printf("%s;%zu;%zu\n", "NiceAgentClass", sizeof(NiceAgentClass), alignof(NiceAgentClass));
|
||||
printf("%s;%zu;%zu\n", "NiceAgentOption", sizeof(NiceAgentOption), alignof(NiceAgentOption));
|
||||
printf("%s;%zu;%zu\n", "NiceCandidate", sizeof(NiceCandidate), alignof(NiceCandidate));
|
||||
printf("%s;%zu;%zu\n", "NiceCandidateTransport", sizeof(NiceCandidateTransport), alignof(NiceCandidateTransport));
|
||||
printf("%s;%zu;%zu\n", "NiceCandidateType", sizeof(NiceCandidateType), alignof(NiceCandidateType));
|
||||
printf("%s;%zu;%zu\n", "NiceCompatibility", sizeof(NiceCompatibility), alignof(NiceCompatibility));
|
||||
printf("%s;%zu;%zu\n", "NiceComponentState", sizeof(NiceComponentState), alignof(NiceComponentState));
|
||||
printf("%s;%zu;%zu\n", "NiceComponentType", sizeof(NiceComponentType), alignof(NiceComponentType));
|
||||
printf("%s;%zu;%zu\n", "NiceInputMessage", sizeof(NiceInputMessage), alignof(NiceInputMessage));
|
||||
printf("%s;%zu;%zu\n", "NiceNominationMode", sizeof(NiceNominationMode), alignof(NiceNominationMode));
|
||||
printf("%s;%zu;%zu\n", "NiceOutputMessage", sizeof(NiceOutputMessage), alignof(NiceOutputMessage));
|
||||
printf("%s;%zu;%zu\n", "NiceProxyType", sizeof(NiceProxyType), alignof(NiceProxyType));
|
||||
printf("%s;%zu;%zu\n", "NiceRelayType", sizeof(NiceRelayType), alignof(NiceRelayType));
|
||||
printf("%s;%zu;%zu\n", "PseudoTcpCallbacks", sizeof(PseudoTcpCallbacks), alignof(PseudoTcpCallbacks));
|
||||
printf("%s;%zu;%zu\n", "PseudoTcpDebugLevel", sizeof(PseudoTcpDebugLevel), alignof(PseudoTcpDebugLevel));
|
||||
printf("%s;%zu;%zu\n", "PseudoTcpShutdown", sizeof(PseudoTcpShutdown), alignof(PseudoTcpShutdown));
|
||||
printf("%s;%zu;%zu\n", "PseudoTcpState", sizeof(PseudoTcpState), alignof(PseudoTcpState));
|
||||
printf("%s;%zu;%zu\n", "PseudoTcpWriteResult", sizeof(PseudoTcpWriteResult), alignof(PseudoTcpWriteResult));
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Feel free to edit this file, it won't be regenerated by gir generator unless removed.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "nice-gst-meet"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
license = "MIT/Apache-2.0"
|
||||
authors = ["Jasper Hugo <jasper@avstack.io>"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = { version = "1", default-features = false, optional = true }
|
||||
glib = { version = "0.14", default-features = false }
|
||||
libc = { version = "0.2", default-features = false }
|
||||
nice-gst-meet-sys = { path = "../nice-gst-meet-sys", default-features = false }
|
||||
nix = { version = "0.22", default-features = false }
|
||||
|
||||
[features]
|
||||
v0_1_4 = ["nice-gst-meet-sys/v0_1_4"]
|
||||
v0_1_5 = ["nice-gst-meet-sys/v0_1_5", "v0_1_4"]
|
||||
v0_1_6 = ["nice-gst-meet-sys/v0_1_6", "v0_1_5"]
|
||||
v0_1_8 = ["nice-gst-meet-sys/v0_1_8", "v0_1_6"]
|
||||
v0_1_14 = ["nice-gst-meet-sys/v0_1_14", "v0_1_8"]
|
||||
v0_1_15 = ["nice-gst-meet-sys/v0_1_15", "v0_1_14", "bitflags"]
|
||||
v0_1_16 = ["nice-gst-meet-sys/v0_1_16", "v0_1_15"]
|
||||
v0_1_17 = ["nice-gst-meet-sys/v0_1_17", "v0_1_16"]
|
||||
v0_1_18 = ["nice-gst-meet-sys/v0_1_18", "v0_1_17"]
|
||||
v0_1_20 = ["nice-gst-meet-sys/v0_1_20", "v0_1_18"]
|
||||
dox = []
|
|
@ -0,0 +1,20 @@
|
|||
[options]
|
||||
library = "Nice"
|
||||
version = "0.1"
|
||||
min_cfg_version = "0.1"
|
||||
work_mode = "normal"
|
||||
auto_path = "src"
|
||||
target_path = "."
|
||||
external_libraries = []
|
||||
generate = [
|
||||
"Nice.Address",
|
||||
"Nice.Agent",
|
||||
"Nice.AgentOption",
|
||||
"Nice.Candidate",
|
||||
"Nice.CandidateTransport",
|
||||
"Nice.CandidateType",
|
||||
"Nice.Compatibility",
|
||||
"Nice.ComponentState",
|
||||
"Nice.RelayType",
|
||||
]
|
||||
manual = ["glib.MainContext"]
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,23 @@
|
|||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,206 @@
|
|||
// Generated by gir (https://github.com/gtk-rs/gir @ 5bbf6cb)
|
||||
// from ../../gir-files (@ 8e47c67)
|
||||
// DO NOT EDIT
|
||||
|
||||
use std::{ffi::CStr, net::SocketAddr};
|
||||
|
||||
use glib::translate::*;
|
||||
use libc::c_char;
|
||||
use nice_sys as ffi;
|
||||
use nix::sys::socket::{AddressFamily, InetAddr};
|
||||
|
||||
#[cfg(any(feature = "v0_1_18", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_18")))]
|
||||
use crate::CandidateTransport;
|
||||
use crate::CandidateType;
|
||||
|
||||
glib::wrapper! {
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Candidate(Boxed<ffi::NiceCandidate>);
|
||||
|
||||
match fn {
|
||||
copy => |ptr| ffi::nice_candidate_copy(ptr),
|
||||
free => |ptr| ffi::nice_candidate_free(ptr),
|
||||
type_ => || ffi::nice_candidate_get_type(),
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for Candidate {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.debug_struct("Candidate")
|
||||
.field("type_", &self.type_())
|
||||
.field("foundation", &self.foundation())
|
||||
.field("transport", &self.transport())
|
||||
.field("addr", &self.addr())
|
||||
.field("priority", &self.priority())
|
||||
.field("stream_id", &self.stream_id())
|
||||
.field("component_id", &self.component_id())
|
||||
.field("username", &self.username())
|
||||
.field("password", &self.password())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Candidate {}
|
||||
|
||||
impl<'a> ToGlibPtr<'a, *mut ffi::NiceCandidate> for Candidate {
|
||||
type Storage = &'a Self;
|
||||
|
||||
#[inline]
|
||||
fn to_glib_none(&'a self) -> Stash<'a, *mut ffi::NiceCandidate, Self> {
|
||||
Stash(&*self.0 as *const _ as *mut _, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Candidate {
|
||||
#[doc(alias = "nice_candidate_new")]
|
||||
pub fn new(type_: CandidateType) -> Candidate {
|
||||
unsafe { from_glib_full(ffi::nice_candidate_new(type_.into_glib())) }
|
||||
}
|
||||
|
||||
pub fn type_(&self) -> CandidateType {
|
||||
unsafe { CandidateType::from_glib(self.0.type_) }
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "v0_1_18", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_18")))]
|
||||
pub fn transport(&self) -> CandidateTransport {
|
||||
unsafe { CandidateTransport::from_glib(self.0.transport) }
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "v0_1_18", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_18")))]
|
||||
pub fn set_transport(&mut self, transport: CandidateTransport) {
|
||||
self.0.transport = transport.into_glib();
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> SocketAddr {
|
||||
unsafe {
|
||||
match AddressFamily::from_i32(self.0.addr.s.addr.sa_family as i32).unwrap() {
|
||||
AddressFamily::Inet => InetAddr::V4(self.0.addr.s.ip4).to_std(),
|
||||
AddressFamily::Inet6 => InetAddr::V6(self.0.addr.s.ip6).to_std(),
|
||||
other => panic!("unsupported address family: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_addr(&mut self, addr: SocketAddr) {
|
||||
match InetAddr::from_std(&addr) {
|
||||
InetAddr::V4(ip4) => unsafe {
|
||||
ffi::nice_address_set_ipv4(
|
||||
&mut self.0.addr as *mut _,
|
||||
u32::from_be(ip4.sin_addr.s_addr),
|
||||
);
|
||||
ffi::nice_address_set_port(
|
||||
&mut self.0.addr as *mut _,
|
||||
u16::from_be(ip4.sin_port) as u32,
|
||||
);
|
||||
},
|
||||
InetAddr::V6(ip6) => unsafe {
|
||||
ffi::nice_address_set_ipv6(
|
||||
&mut self.0.addr as *mut _,
|
||||
&ip6.sin6_addr.s6_addr as *const _,
|
||||
);
|
||||
ffi::nice_address_set_port(
|
||||
&mut self.0.addr as *mut _,
|
||||
u16::from_be(ip6.sin6_port) as u32,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn priority(&self) -> u32 {
|
||||
self.0.priority
|
||||
}
|
||||
|
||||
pub fn set_priority(&mut self, priority: u32) {
|
||||
self.0.priority = priority;
|
||||
}
|
||||
|
||||
pub fn stream_id(&self) -> u32 {
|
||||
self.0.stream_id
|
||||
}
|
||||
|
||||
pub fn set_stream_id(&mut self, stream_id: u32) {
|
||||
self.0.stream_id = stream_id;
|
||||
}
|
||||
|
||||
pub fn component_id(&self) -> u32 {
|
||||
self.0.component_id
|
||||
}
|
||||
|
||||
pub fn set_component_id(&mut self, component_id: u32) {
|
||||
self.0.component_id = component_id;
|
||||
}
|
||||
|
||||
pub fn foundation(&self) -> Result<&str, std::str::Utf8Error> {
|
||||
unsafe { CStr::from_ptr(&self.0.foundation as *const c_char).to_str() }
|
||||
}
|
||||
|
||||
pub fn set_foundation(&mut self, foundation: &str) {
|
||||
let mut bytes: Vec<_> = foundation
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.take(32)
|
||||
.map(|c| *c as i8)
|
||||
.collect();
|
||||
bytes.resize(33, 0);
|
||||
self.0.foundation.copy_from_slice(&bytes);
|
||||
}
|
||||
|
||||
pub fn username(&self) -> Result<&str, std::str::Utf8Error> {
|
||||
if self.0.username.is_null() {
|
||||
Ok("")
|
||||
}
|
||||
else {
|
||||
unsafe { CStr::from_ptr(self.0.username).to_str() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_username(&mut self, username: &str) {
|
||||
self.0.username = username.to_owned().to_glib_full();
|
||||
}
|
||||
|
||||
pub fn password(&self) -> Result<&str, std::str::Utf8Error> {
|
||||
if self.0.password.is_null() {
|
||||
Ok("")
|
||||
}
|
||||
else {
|
||||
unsafe { CStr::from_ptr(self.0.password).to_str() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_password(&mut self, password: &str) {
|
||||
self.0.password = password.to_owned().to_glib_full();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
#[doc(alias = "nice_candidate_equal_target")]
|
||||
pub fn equal_target(&self, candidate2: &Candidate) -> bool {
|
||||
unsafe {
|
||||
from_glib(ffi::nice_candidate_equal_target(
|
||||
self.to_glib_none().0,
|
||||
candidate2.to_glib_none().0,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "v0_1_18", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_18")))]
|
||||
#[doc(alias = "nice_candidate_transport_to_string")]
|
||||
pub fn transport_to_string(transport: CandidateTransport) -> Option<String> {
|
||||
unsafe {
|
||||
from_glib_none(ffi::nice_candidate_transport_to_string(
|
||||
transport.into_glib(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "v0_1_18", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_18")))]
|
||||
#[doc(alias = "nice_candidate_type_to_string")]
|
||||
pub fn type_to_string(type_: CandidateType) -> Option<String> {
|
||||
unsafe { from_glib_none(ffi::nice_candidate_type_to_string(type_.into_glib())) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
// Generated by gir (https://github.com/gtk-rs/gir @ 5bbf6cb)
|
||||
// from ../../gir-files (@ 8e47c67)
|
||||
// DO NOT EDIT
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use glib::translate::*;
|
||||
use nice_sys as ffi;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
#[doc(alias = "NiceCandidateTransport")]
|
||||
pub enum CandidateTransport {
|
||||
#[doc(alias = "NICE_CANDIDATE_TRANSPORT_UDP")]
|
||||
Udp,
|
||||
#[doc(alias = "NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE")]
|
||||
TcpActive,
|
||||
#[doc(alias = "NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE")]
|
||||
TcpPassive,
|
||||
#[doc(alias = "NICE_CANDIDATE_TRANSPORT_TCP_SO")]
|
||||
TcpSo,
|
||||
#[doc(hidden)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
impl fmt::Display for CandidateTransport {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"CandidateTransport::{}",
|
||||
match *self {
|
||||
Self::Udp => "Udp",
|
||||
Self::TcpActive => "TcpActive",
|
||||
Self::TcpPassive => "TcpPassive",
|
||||
Self::TcpSo => "TcpSo",
|
||||
_ => "Unknown",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl IntoGlib for CandidateTransport {
|
||||
type GlibType = ffi::NiceCandidateTransport;
|
||||
|
||||
fn into_glib(self) -> ffi::NiceCandidateTransport {
|
||||
match self {
|
||||
Self::Udp => ffi::NICE_CANDIDATE_TRANSPORT_UDP,
|
||||
Self::TcpActive => ffi::NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE,
|
||||
Self::TcpPassive => ffi::NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE,
|
||||
Self::TcpSo => ffi::NICE_CANDIDATE_TRANSPORT_TCP_SO,
|
||||
Self::__Unknown(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromGlib<ffi::NiceCandidateTransport> for CandidateTransport {
|
||||
unsafe fn from_glib(value: ffi::NiceCandidateTransport) -> Self {
|
||||
match value {
|
||||
ffi::NICE_CANDIDATE_TRANSPORT_UDP => Self::Udp,
|
||||
ffi::NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE => Self::TcpActive,
|
||||
ffi::NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE => Self::TcpPassive,
|
||||
ffi::NICE_CANDIDATE_TRANSPORT_TCP_SO => Self::TcpSo,
|
||||
value => Self::__Unknown(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
#[doc(alias = "NiceCandidateType")]
|
||||
pub enum CandidateType {
|
||||
#[doc(alias = "NICE_CANDIDATE_TYPE_HOST")]
|
||||
Host,
|
||||
#[doc(alias = "NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE")]
|
||||
ServerReflexive,
|
||||
#[doc(alias = "NICE_CANDIDATE_TYPE_PEER_REFLEXIVE")]
|
||||
PeerReflexive,
|
||||
#[doc(alias = "NICE_CANDIDATE_TYPE_RELAYED")]
|
||||
Relayed,
|
||||
#[doc(hidden)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
impl fmt::Display for CandidateType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"CandidateType::{}",
|
||||
match *self {
|
||||
Self::Host => "Host",
|
||||
Self::ServerReflexive => "ServerReflexive",
|
||||
Self::PeerReflexive => "PeerReflexive",
|
||||
Self::Relayed => "Relayed",
|
||||
_ => "Unknown",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl IntoGlib for CandidateType {
|
||||
type GlibType = ffi::NiceCandidateType;
|
||||
|
||||
fn into_glib(self) -> ffi::NiceCandidateType {
|
||||
match self {
|
||||
Self::Host => ffi::NICE_CANDIDATE_TYPE_HOST,
|
||||
Self::ServerReflexive => ffi::NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
|
||||
Self::PeerReflexive => ffi::NICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
|
||||
Self::Relayed => ffi::NICE_CANDIDATE_TYPE_RELAYED,
|
||||
Self::__Unknown(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromGlib<ffi::NiceCandidateType> for CandidateType {
|
||||
unsafe fn from_glib(value: ffi::NiceCandidateType) -> Self {
|
||||
match value {
|
||||
ffi::NICE_CANDIDATE_TYPE_HOST => Self::Host,
|
||||
ffi::NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE => Self::ServerReflexive,
|
||||
ffi::NICE_CANDIDATE_TYPE_PEER_REFLEXIVE => Self::PeerReflexive,
|
||||
ffi::NICE_CANDIDATE_TYPE_RELAYED => Self::Relayed,
|
||||
value => Self::__Unknown(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
#[doc(alias = "NiceCompatibility")]
|
||||
pub enum Compatibility {
|
||||
#[doc(alias = "NICE_COMPATIBILITY_RFC5245")]
|
||||
Rfc5245,
|
||||
#[doc(alias = "NICE_COMPATIBILITY_GOOGLE")]
|
||||
Google,
|
||||
#[doc(alias = "NICE_COMPATIBILITY_MSN")]
|
||||
Msn,
|
||||
#[doc(alias = "NICE_COMPATIBILITY_WLM2009")]
|
||||
Wlm2009,
|
||||
#[doc(alias = "NICE_COMPATIBILITY_OC2007")]
|
||||
Oc2007,
|
||||
#[doc(alias = "NICE_COMPATIBILITY_OC2007R2")]
|
||||
Oc2007r2,
|
||||
#[doc(hidden)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
impl fmt::Display for Compatibility {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Compatibility::{}",
|
||||
match *self {
|
||||
Self::Rfc5245 => "Rfc5245",
|
||||
Self::Google => "Google",
|
||||
Self::Msn => "Msn",
|
||||
Self::Wlm2009 => "Wlm2009",
|
||||
Self::Oc2007 => "Oc2007",
|
||||
Self::Oc2007r2 => "Oc2007r2",
|
||||
_ => "Unknown",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl IntoGlib for Compatibility {
|
||||
type GlibType = ffi::NiceCompatibility;
|
||||
|
||||
fn into_glib(self) -> ffi::NiceCompatibility {
|
||||
match self {
|
||||
Self::Rfc5245 => ffi::NICE_COMPATIBILITY_RFC5245,
|
||||
Self::Google => ffi::NICE_COMPATIBILITY_GOOGLE,
|
||||
Self::Msn => ffi::NICE_COMPATIBILITY_MSN,
|
||||
Self::Wlm2009 => ffi::NICE_COMPATIBILITY_WLM2009,
|
||||
Self::Oc2007 => ffi::NICE_COMPATIBILITY_OC2007,
|
||||
Self::Oc2007r2 => ffi::NICE_COMPATIBILITY_OC2007R2,
|
||||
Self::__Unknown(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromGlib<ffi::NiceCompatibility> for Compatibility {
|
||||
unsafe fn from_glib(value: ffi::NiceCompatibility) -> Self {
|
||||
match value {
|
||||
ffi::NICE_COMPATIBILITY_RFC5245 => Self::Rfc5245,
|
||||
ffi::NICE_COMPATIBILITY_GOOGLE => Self::Google,
|
||||
ffi::NICE_COMPATIBILITY_MSN => Self::Msn,
|
||||
ffi::NICE_COMPATIBILITY_WLM2009 => Self::Wlm2009,
|
||||
ffi::NICE_COMPATIBILITY_OC2007 => Self::Oc2007,
|
||||
ffi::NICE_COMPATIBILITY_OC2007R2 => Self::Oc2007r2,
|
||||
value => Self::__Unknown(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
#[doc(alias = "NiceComponentState")]
|
||||
pub enum ComponentState {
|
||||
#[doc(alias = "NICE_COMPONENT_STATE_DISCONNECTED")]
|
||||
Disconnected,
|
||||
#[doc(alias = "NICE_COMPONENT_STATE_GATHERING")]
|
||||
Gathering,
|
||||
#[doc(alias = "NICE_COMPONENT_STATE_CONNECTING")]
|
||||
Connecting,
|
||||
#[doc(alias = "NICE_COMPONENT_STATE_CONNECTED")]
|
||||
Connected,
|
||||
#[doc(alias = "NICE_COMPONENT_STATE_READY")]
|
||||
Ready,
|
||||
#[doc(alias = "NICE_COMPONENT_STATE_FAILED")]
|
||||
Failed,
|
||||
#[doc(alias = "NICE_COMPONENT_STATE_LAST")]
|
||||
Last,
|
||||
#[doc(hidden)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
impl fmt::Display for ComponentState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"ComponentState::{}",
|
||||
match *self {
|
||||
Self::Disconnected => "Disconnected",
|
||||
Self::Gathering => "Gathering",
|
||||
Self::Connecting => "Connecting",
|
||||
Self::Connected => "Connected",
|
||||
Self::Ready => "Ready",
|
||||
Self::Failed => "Failed",
|
||||
Self::Last => "Last",
|
||||
_ => "Unknown",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl IntoGlib for ComponentState {
|
||||
type GlibType = ffi::NiceComponentState;
|
||||
|
||||
fn into_glib(self) -> ffi::NiceComponentState {
|
||||
match self {
|
||||
Self::Disconnected => ffi::NICE_COMPONENT_STATE_DISCONNECTED,
|
||||
Self::Gathering => ffi::NICE_COMPONENT_STATE_GATHERING,
|
||||
Self::Connecting => ffi::NICE_COMPONENT_STATE_CONNECTING,
|
||||
Self::Connected => ffi::NICE_COMPONENT_STATE_CONNECTED,
|
||||
Self::Ready => ffi::NICE_COMPONENT_STATE_READY,
|
||||
Self::Failed => ffi::NICE_COMPONENT_STATE_FAILED,
|
||||
Self::Last => ffi::NICE_COMPONENT_STATE_LAST,
|
||||
Self::__Unknown(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromGlib<ffi::NiceComponentState> for ComponentState {
|
||||
unsafe fn from_glib(value: ffi::NiceComponentState) -> Self {
|
||||
match value {
|
||||
ffi::NICE_COMPONENT_STATE_DISCONNECTED => Self::Disconnected,
|
||||
ffi::NICE_COMPONENT_STATE_GATHERING => Self::Gathering,
|
||||
ffi::NICE_COMPONENT_STATE_CONNECTING => Self::Connecting,
|
||||
ffi::NICE_COMPONENT_STATE_CONNECTED => Self::Connected,
|
||||
ffi::NICE_COMPONENT_STATE_READY => Self::Ready,
|
||||
ffi::NICE_COMPONENT_STATE_FAILED => Self::Failed,
|
||||
ffi::NICE_COMPONENT_STATE_LAST => Self::Last,
|
||||
value => Self::__Unknown(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
#[doc(alias = "NiceRelayType")]
|
||||
pub enum RelayType {
|
||||
#[doc(alias = "NICE_RELAY_TYPE_TURN_UDP")]
|
||||
Udp,
|
||||
#[doc(alias = "NICE_RELAY_TYPE_TURN_TCP")]
|
||||
Tcp,
|
||||
#[doc(alias = "NICE_RELAY_TYPE_TURN_TLS")]
|
||||
Tls,
|
||||
#[doc(hidden)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
impl fmt::Display for RelayType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"RelayType::{}",
|
||||
match *self {
|
||||
Self::Udp => "Udp",
|
||||
Self::Tcp => "Tcp",
|
||||
Self::Tls => "Tls",
|
||||
_ => "Unknown",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl IntoGlib for RelayType {
|
||||
type GlibType = ffi::NiceRelayType;
|
||||
|
||||
fn into_glib(self) -> ffi::NiceRelayType {
|
||||
match self {
|
||||
Self::Udp => ffi::NICE_RELAY_TYPE_TURN_UDP,
|
||||
Self::Tcp => ffi::NICE_RELAY_TYPE_TURN_TCP,
|
||||
Self::Tls => ffi::NICE_RELAY_TYPE_TURN_TLS,
|
||||
Self::__Unknown(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromGlib<ffi::NiceRelayType> for RelayType {
|
||||
unsafe fn from_glib(value: ffi::NiceRelayType) -> Self {
|
||||
match value {
|
||||
ffi::NICE_RELAY_TYPE_TURN_UDP => Self::Udp,
|
||||
ffi::NICE_RELAY_TYPE_TURN_TCP => Self::Tcp,
|
||||
ffi::NICE_RELAY_TYPE_TURN_TLS => Self::Tls,
|
||||
value => Self::__Unknown(value),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Generated by gir (https://github.com/gtk-rs/gir @ 5bbf6cb)
|
||||
// from ../../gir-files (@ 8e47c67)
|
||||
// DO NOT EDIT
|
||||
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
use std::fmt;
|
||||
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
use bitflags::bitflags;
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
use glib::translate::*;
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
use nice_sys as ffi;
|
||||
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
bitflags! {
|
||||
#[doc(alias = "NiceAgentOption")]
|
||||
pub struct AgentOption: u32 {
|
||||
#[doc(alias = "NICE_AGENT_OPTION_REGULAR_NOMINATION")]
|
||||
const REGULAR_NOMINATION = ffi::NICE_AGENT_OPTION_REGULAR_NOMINATION as u32;
|
||||
#[doc(alias = "NICE_AGENT_OPTION_RELIABLE")]
|
||||
const RELIABLE = ffi::NICE_AGENT_OPTION_RELIABLE as u32;
|
||||
#[doc(alias = "NICE_AGENT_OPTION_LITE_MODE")]
|
||||
const LITE_MODE = ffi::NICE_AGENT_OPTION_LITE_MODE as u32;
|
||||
#[doc(alias = "NICE_AGENT_OPTION_ICE_TRICKLE")]
|
||||
const ICE_TRICKLE = ffi::NICE_AGENT_OPTION_ICE_TRICKLE as u32;
|
||||
#[doc(alias = "NICE_AGENT_OPTION_SUPPORT_RENOMINATION")]
|
||||
const SUPPORT_RENOMINATION = ffi::NICE_AGENT_OPTION_SUPPORT_RENOMINATION as u32;
|
||||
#[doc(alias = "NICE_AGENT_OPTION_CONSENT_FRESHNESS")]
|
||||
const CONSENT_FRESHNESS = ffi::NICE_AGENT_OPTION_CONSENT_FRESHNESS as u32;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
impl fmt::Display for AgentOption {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
<Self as fmt::Debug>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
#[doc(hidden)]
|
||||
impl IntoGlib for AgentOption {
|
||||
type GlibType = ffi::NiceAgentOption;
|
||||
|
||||
fn into_glib(self) -> ffi::NiceAgentOption {
|
||||
self.bits()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
#[doc(hidden)]
|
||||
impl FromGlib<ffi::NiceAgentOption> for AgentOption {
|
||||
unsafe fn from_glib(value: ffi::NiceAgentOption) -> Self {
|
||||
Self::from_bits_truncate(value)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Generated by gir (https://github.com/gtk-rs/gir @ 5bbf6cb)
|
||||
// from ../../gir-files (@ 8e47c67)
|
||||
// DO NOT EDIT
|
||||
|
||||
mod agent;
|
||||
pub use self::agent::Agent;
|
||||
|
||||
mod candidate;
|
||||
pub use self::candidate::Candidate;
|
||||
|
||||
mod enums;
|
||||
pub use self::enums::{
|
||||
CandidateTransport, CandidateType, Compatibility, ComponentState, RelayType,
|
||||
};
|
||||
|
||||
mod flags;
|
||||
use nice_sys as ffi;
|
||||
|
||||
#[cfg(any(feature = "v0_1_15", feature = "dox"))]
|
||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v0_1_15")))]
|
||||
pub use self::flags::AgentOption;
|
||||
|
||||
pub fn debug_enable(with_stun: bool) {
|
||||
unsafe {
|
||||
ffi::nice_debug_enable(with_stun as i32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_disable(with_stun: bool) {
|
||||
unsafe {
|
||||
ffi::nice_debug_disable(with_stun as i32);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
tab_spaces = 2
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
control_brace_style = "ClosingNextLine"
|
||||
condense_wildcard_suffixes = true
|
||||
match_block_trailing_comma = true
|
||||
imports_granularity = "Crate"
|
||||
newline_style = "Unix"
|
||||
reorder_impl_items = true
|
||||
group_imports = "StdExternalCrate"
|
||||
report_fixme = "Unnumbered"
|
||||
report_todo = "Unnumbered"
|
||||
version = "Two"
|
Loading…
Reference in New Issue