This commit is contained in:
Jasper Hugo 2021-08-13 18:48:59 +07:00
commit fbaf22ba7e
46 changed files with 9450 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1790
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

5
Cargo.toml Normal file
View File

@ -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" }

201
LICENSE-APACHE Normal file
View File

@ -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.

23
LICENSE-MIT Normal file
View File

@ -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.

118
README.md Normal file
View File

@ -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.

44
deny.toml Normal file
View File

@ -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"]

28
gst-meet/Cargo.toml Normal file
View File

@ -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 }

201
gst-meet/LICENSE-APACHE Normal file
View File

@ -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.

23
gst-meet/LICENSE-MIT Normal file
View File

@ -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.

224
gst-meet/src/main.rs Normal file
View File

@ -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();
}

34
lib-gst-meet/Cargo.toml Normal file
View File

@ -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 }

201
lib-gst-meet/LICENSE-APACHE Normal file
View File

@ -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.

23
lib-gst-meet/LICENSE-MIT Normal file
View File

@ -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.

View File

@ -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(())
}
}

View File

@ -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;
}
}
},
}
}
}
}

827
lib-gst-meet/src/jingle.rs Normal file
View File

@ -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(())
}
}

14
lib-gst-meet/src/lib.rs Normal file
View File

@ -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},
};

View File

@ -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(())
}
}

View File

@ -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",
}
}
}

View File

@ -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<()>;
}

20
lib-gst-meet/src/util.rs Normal file
View File

@ -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()?),
})
}
}

View File

@ -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<_>>()?,
})
}
}

View File

@ -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()
}
}

View File

@ -0,0 +1,3 @@
pub(crate) mod extdisco;
pub(crate) mod jitsi;
mod ns;

View File

@ -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";

View File

@ -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 = []

View File

@ -0,0 +1,8 @@
[options]
library = "Nice"
version = "0.1"
min_cfg_version = "0.1"
work_mode = "sys"
target_path = "."
external_libraries = []

View File

@ -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.

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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"),
];

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,2 @@
// Feel free to edit this file, it won't be regenerated by gir generator unless removed.

26
nice-gst-meet/Cargo.toml Normal file
View File

@ -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 = []

20
nice-gst-meet/Gir.toml Normal file
View File

@ -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"]

View File

@ -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.

23
nice-gst-meet/LICENSE-MIT Normal file
View File

@ -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.

2130
nice-gst-meet/src/agent.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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())) }
}
}

328
nice-gst-meet/src/enums.rs Normal file
View File

@ -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),
}
}
}

View File

@ -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)
}
}

33
nice-gst-meet/src/lib.rs Normal file
View File

@ -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);
}
}

13
rustfmt.toml Normal file
View File

@ -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"