diff --git a/CHANGELOG.md b/CHANGELOG.md index b3e6872b86..4345cf2ded 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All user visible changes to this project will be documented in this file. This p ### Added - `RtpTransceiverInit.sendEncodings` field with `SendEncodingParameters`. ([#125]) +- `MediaStreamTrack.height()` and `MediaStreamTrack.width()` methods. ([#129]) - `RtpParameters` class, `RtpSender.getParameters()` and `RtpSender.setParameters()` methods. ([#135]) ### Changed @@ -34,6 +35,7 @@ All user visible changes to this project will be documented in this file. This p [#123]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/123 [#124]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/124 [#125]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/125 +[#129]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/129 [#133]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/133 [#135]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/135 [#136]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/136 diff --git a/Cargo.lock b/Cargo.lock index 05f1bb8c80..4ee81c7d45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,9 +145,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -155,9 +155,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" @@ -293,12 +293,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -316,7 +316,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall 0.3.5", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -382,9 +382,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -449,9 +449,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" @@ -474,9 +474,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "hermit-abi" @@ -557,9 +557,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -589,9 +589,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -682,9 +682,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" @@ -755,7 +755,7 @@ checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -834,9 +834,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" dependencies = [ "bitflags 2.4.1", "cfg-if", @@ -866,9 +866,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" dependencies = [ "cc", "libc", @@ -901,9 +901,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -925,9 +925,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -1014,15 +1014,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1046,7 +1046,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1176,7 +1176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1243,7 +1243,7 @@ dependencies = [ "fastrand", "redox_syscall 0.4.1", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1292,7 +1292,7 @@ dependencies = [ "num_cpus", "pin-project-lite", "socket2 0.5.5", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1385,9 +1385,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -1433,9 +1433,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1443,9 +1443,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -1458,9 +1458,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -1470,9 +1470,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1480,9 +1480,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -1493,15 +1493,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -1590,6 +1590,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -1711,7 +1720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] diff --git a/android/src/main/kotlin/com/instrumentisto/medea_flutter_webrtc/controller/MediaStreamTrackController.kt b/android/src/main/kotlin/com/instrumentisto/medea_flutter_webrtc/controller/MediaStreamTrackController.kt index ae4d866ed4..b365f186bd 100644 --- a/android/src/main/kotlin/com/instrumentisto/medea_flutter_webrtc/controller/MediaStreamTrackController.kt +++ b/android/src/main/kotlin/com/instrumentisto/medea_flutter_webrtc/controller/MediaStreamTrackController.kt @@ -6,6 +6,9 @@ import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch /** * Controller of [MediaStreamTrackProxy] functional. @@ -55,6 +58,12 @@ class MediaStreamTrackController( "state" -> { result.success(track.state.value) } + "width" -> { + GlobalScope.launch(Dispatchers.Main) { result.success(track.width()) } + } + "height" -> { + GlobalScope.launch(Dispatchers.Main) { result.success(track.height()) } + } "stop" -> { track.stop() result.success(null) diff --git a/android/src/main/kotlin/com/instrumentisto/medea_flutter_webrtc/proxy/MediaStreamTrackProxy.kt b/android/src/main/kotlin/com/instrumentisto/medea_flutter_webrtc/proxy/MediaStreamTrackProxy.kt index 60e12e3969..7c630aae3b 100644 --- a/android/src/main/kotlin/com/instrumentisto/medea_flutter_webrtc/proxy/MediaStreamTrackProxy.kt +++ b/android/src/main/kotlin/com/instrumentisto/medea_flutter_webrtc/proxy/MediaStreamTrackProxy.kt @@ -5,7 +5,10 @@ import com.instrumentisto.medea_flutter_webrtc.TrackRepository import com.instrumentisto.medea_flutter_webrtc.model.FacingMode import com.instrumentisto.medea_flutter_webrtc.model.MediaStreamTrackState import com.instrumentisto.medea_flutter_webrtc.model.MediaType +import kotlinx.coroutines.CompletableDeferred import org.webrtc.MediaStreamTrack +import org.webrtc.VideoSink +import org.webrtc.VideoTrack /** * Wrapper around a [MediaStreamTrack]. @@ -37,6 +40,18 @@ class MediaStreamTrackProxy( /** Indicator whether the underlying [MediaStreamTrack] had been disposed. */ private var disposed: Boolean = false + /** [VideoSink] that tracks height and width changes. */ + private lateinit var sink: VideoSink + + /** Provides asynchronous wait for [width] and [height] initialization. */ + private val fetchDimensions = CompletableDeferred() + + /** Video width */ + @Volatile private var width: Int = 0 + + /** Video height */ + @Volatile private var height: Int = 0 + /** [MediaType] of the underlying [MediaStreamTrack]. */ val kind: MediaType = when (obj.kind()) { @@ -63,6 +78,36 @@ class MediaStreamTrackProxy( init { TrackRepository.addTrack(this) + + if (kind == MediaType.VIDEO) { + sink = VideoSink { frame -> + width = frame.buffer.width + height = frame.buffer.height + fetchDimensions.complete(Unit) + } + (obj as VideoTrack).addSink(sink) + + addOnSyncListener { (obj as VideoTrack).addSink(sink) } + } + } + + /** Returns the video [width] of the track. */ + suspend fun width(): Int? { + if (kind == MediaType.AUDIO) { + return null + } + + fetchDimensions.await() + return width + } + + /** Returns the video [height] of the track. */ + suspend fun height(): Int? { + if (kind == MediaType.AUDIO) { + return null + } + fetchDimensions.await() + return height } /** Sets the [disposed] property to `true`. */ diff --git a/crates/native/Cargo.toml b/crates/native/Cargo.toml index a12b4d509d..2883c54a53 100644 --- a/crates/native/Cargo.toml +++ b/crates/native/Cargo.toml @@ -2,7 +2,7 @@ name = "medea-flutter-webrtc-native" version = "0.0.0" edition = "2021" -rust-version = "1.62" +rust-version = "1.70" publish = false [lib] diff --git a/crates/native/src/api.rs b/crates/native/src/api.rs index 675720c30e..b8288983b2 100644 --- a/crates/native/src/api.rs +++ b/crates/native/src/api.rs @@ -1599,7 +1599,7 @@ pub struct MediaDisplayInfo { /// [MediaStreamConstraints], used to instruct what sort of /// [`MediaStreamTrack`]s to include in the [`MediaStream`] returned by -/// [`Webrtc::get_users_media()`]. +/// [`Webrtc::get_media()`]. /// /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamconstraints #[derive(Debug)] @@ -1612,7 +1612,7 @@ pub struct MediaStreamConstraints { } /// Nature and settings of the video [`MediaStreamTrack`] returned by -/// [`Webrtc::get_users_media()`]. +/// [`Webrtc::get_media()`]. #[derive(Debug)] pub struct VideoConstraints { /// Identifier of the device generating the content of the @@ -1636,7 +1636,7 @@ pub struct VideoConstraints { } /// Nature and settings of the audio [`MediaStreamTrack`] returned by -/// [`Webrtc::get_users_media()`]. +/// [`Webrtc::get_media()`]. #[derive(Debug)] pub struct AudioConstraints { /// Identifier of the device generating the content of the @@ -2260,6 +2260,53 @@ pub fn track_state( .track_state(track_id, track_origin, kind) } +/// Returns the [height] property of the media track by its ID and +/// [`MediaType`]. +/// +/// Blocks until the [height] is initialized. +/// +/// [height]: https://w3.org/TR/mediacapture-streams#dfn-height +pub fn track_height( + track_id: String, + peer_id: Option, + kind: MediaType, +) -> anyhow::Result> { + if kind == MediaType::Audio { + return Ok(None); + } + + let track_origin = TrackOrigin::from(peer_id.map(PeerConnectionId::from)); + + WEBRTC + .lock() + .unwrap() + .track_height(track_id, track_origin) + .map(Some) +} + +/// Returns the [width] property of the media track by its ID and [`MediaType`]. +/// +/// Blocks until the [width] is initialized. +/// +/// [width]: https://w3.org/TR/mediacapture-streams#dfn-height +pub fn track_width( + track_id: String, + peer_id: Option, + kind: MediaType, +) -> anyhow::Result> { + if kind == MediaType::Audio { + return Ok(None); + } + + let track_origin = TrackOrigin::from(peer_id.map(PeerConnectionId::from)); + + WEBRTC + .lock() + .unwrap() + .track_width(track_id, track_origin) + .map(Some) +} + /// Changes the [enabled][1] property of the [`MediaStreamTrack`] by its ID and /// [`MediaType`]. /// diff --git a/crates/native/src/bridge_generated.rs b/crates/native/src/bridge_generated.rs index 83ce883c99..e03861759a 100644 --- a/crates/native/src/bridge_generated.rs +++ b/crates/native/src/bridge_generated.rs @@ -554,6 +554,46 @@ fn wire_track_state_impl( }, ) } +fn wire_track_height_impl( + port_: MessagePort, + track_id: impl Wire2Api + UnwindSafe, + peer_id: impl Wire2Api> + UnwindSafe, + kind: impl Wire2Api + UnwindSafe, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, Option, _>( + WrapInfo { + debug_name: "track_height", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_track_id = track_id.wire2api(); + let api_peer_id = peer_id.wire2api(); + let api_kind = kind.wire2api(); + move |task_callback| track_height(api_track_id, api_peer_id, api_kind) + }, + ) +} +fn wire_track_width_impl( + port_: MessagePort, + track_id: impl Wire2Api + UnwindSafe, + peer_id: impl Wire2Api> + UnwindSafe, + kind: impl Wire2Api + UnwindSafe, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, Option, _>( + WrapInfo { + debug_name: "track_width", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_track_id = track_id.wire2api(); + let api_peer_id = peer_id.wire2api(); + let api_kind = kind.wire2api(); + move |task_callback| track_width(api_track_id, api_peer_id, api_kind) + }, + ) +} fn wire_set_track_enabled_impl( port_: MessagePort, track_id: impl Wire2Api + UnwindSafe, @@ -1851,6 +1891,26 @@ mod io { wire_track_state_impl(port_, track_id, peer_id, kind) } + #[no_mangle] + pub extern "C" fn wire_track_height( + port_: i64, + track_id: *mut wire_uint_8_list, + peer_id: *mut u64, + kind: i32, + ) { + wire_track_height_impl(port_, track_id, peer_id, kind) + } + + #[no_mangle] + pub extern "C" fn wire_track_width( + port_: i64, + track_id: *mut wire_uint_8_list, + peer_id: *mut u64, + kind: i32, + ) { + wire_track_width_impl(port_, track_id, peer_id, kind) + } + #[no_mangle] pub extern "C" fn wire_set_track_enabled( port_: i64, diff --git a/crates/native/src/devices.rs b/crates/native/src/devices.rs index b5e884e6ba..80cba1a9db 100644 --- a/crates/native/src/devices.rs +++ b/crates/native/src/devices.rs @@ -398,9 +398,8 @@ pub mod linux_device_change { loop { ppoll(&mut [fds], None, None)?; - let event = match socket.receive_event() { - Some(evt) => evt, - None => continue, + let Some(event) = socket.receive_event() else { + continue; }; if matches!( diff --git a/crates/native/src/pc.rs b/crates/native/src/pc.rs index 74033f321f..4a028427b4 100644 --- a/crates/native/src/pc.rs +++ b/crates/native/src/pc.rs @@ -3,7 +3,7 @@ use std::{ mem, sync::{ atomic::{AtomicBool, Ordering}, - mpsc, Arc, Mutex, Weak, + mpsc, Arc, Mutex, OnceLock, Weak, }, }; @@ -13,7 +13,6 @@ use dashmap::DashMap; use derive_more::{Display, From, Into}; use flutter_rust_bridge::RustOpaque; use libwebrtc_sys as sys; -use once_cell::sync::OnceCell; use threadpool::ThreadPool; use crate::{ @@ -273,7 +272,7 @@ impl PeerConnection { configuration: api::RtcConfiguration, pool: ThreadPool, ) -> anyhow::Result> { - let obs_peer = Arc::new(OnceCell::new()); + let obs_peer = Arc::new(OnceLock::new()); let observer = sys::PeerConnectionObserver::new(Box::new( PeerConnectionObserver { observer: Arc::new(Mutex::new(observer)), @@ -1055,7 +1054,7 @@ struct PeerConnectionObserver { /// /// Tasks with [`InnerPeer`] must be offloaded to a separate [`ThreadPool`], /// so the signalling thread wouldn't be blocked. - peer: Arc>>, + peer: Arc>>, /// Map of the remote [`VideoTrack`]s shared with the [`crate::Webrtc`]. video_tracks: Arc>, @@ -1163,9 +1162,7 @@ impl sys::PeerConnectionEventsHandler for PeerConnectionObserver { let audio_tracks = Arc::clone(&self.audio_tracks); move || { - let peer = if let Some(peer) = peer.get().unwrap().upgrade() { - peer - } else { + let Some(peer) = peer.get().unwrap().upgrade() else { // `peer` is already dropped on the Rust side, so just don't // do anything. return; @@ -1175,10 +1172,9 @@ impl sys::PeerConnectionEventsHandler for PeerConnectionObserver { let track = match transceiver.media_type() { sys::MediaType::MEDIA_TYPE_AUDIO => { let track_id = AudioTrackId::from(track_id); - if audio_tracks.contains_key(&( - track_id.clone(), - track_origin.clone(), - )) { + if audio_tracks + .contains_key(&(track_id.clone(), track_origin)) + { return; } @@ -1191,10 +1187,9 @@ impl sys::PeerConnectionEventsHandler for PeerConnectionObserver { } sys::MediaType::MEDIA_TYPE_VIDEO => { let track_id = VideoTrackId::from(track_id); - if video_tracks.contains_key(&( - track_id.clone(), - track_origin.clone(), - )) { + if video_tracks + .contains_key(&(track_id.clone(), track_origin)) + { return; } diff --git a/crates/native/src/user_media.rs b/crates/native/src/user_media.rs index b978777816..5c2a385c45 100644 --- a/crates/native/src/user_media.rs +++ b/crates/native/src/user_media.rs @@ -1,13 +1,14 @@ use std::{ collections::{HashMap, HashSet}, hash::Hash, - sync::{Arc, Weak}, + sync::{Arc, RwLock, Weak}, }; use anyhow::{anyhow, bail, Context}; use derive_more::{AsRef, Display, From, Into}; -use libwebrtc_sys as sys; -use sys::TrackEventObserver; +use libwebrtc_sys::{self as sys, OnFrameCallback, TrackEventObserver}; +// TODO: Use `std::sync::OnceLock` instead, once it support `.wait()` API. +use once_cell::sync::OnceCell; use xxhash::xxh3::xxh3_64; use crate::{ @@ -119,12 +120,12 @@ impl Webrtc { track.remove_video_sink(sink); } } - if let MediaTrackSource::Local(src) = track.source { - if Arc::strong_count(&src) == 2 { + if let MediaTrackSource::Local(src) = &track.source { + if Arc::strong_count(src) == 2 { self.video_sources.remove(&src.device_id); }; } - track.senders + track.senders.clone() } else { return; } @@ -301,11 +302,9 @@ impl Webrtc { } }; - let device_index = if let Some(index) = + let Some(device_index) = self.get_index_of_audio_recording_device(&device_id)? - { - index - } else { + else { bail!( "Cannot find audio device with the specified ID `{device_id}`", ); @@ -363,6 +362,50 @@ impl Webrtc { }) } + /// Returns the [width] property of the media track by its ID and origin. + /// + /// Blocks until the [width] is initialized. + /// + /// [width]: https://w3.org/TR/mediacapture-streams#dfn-width + pub fn track_width( + &self, + id: String, + track_origin: TrackOrigin, + ) -> anyhow::Result { + let id = VideoTrackId::from(id); + + Ok(*self + .video_tracks + .get(&(id.clone(), track_origin)) + .ok_or_else(|| anyhow!("Cannot find video track with ID `{id}`"))? + .width + .wait() + .read() + .unwrap()) + } + + /// Returns the [height] property of the media track by its ID and origin. + /// + /// Blocks until the [height] is initialized. + /// + /// [height]: https://w3.org/TR/mediacapture-streams#dfn-height + pub fn track_height( + &self, + id: String, + track_origin: TrackOrigin, + ) -> anyhow::Result { + let id = VideoTrackId::from(id); + + Ok(*self + .video_tracks + .get(&(id.clone(), track_origin)) + .ok_or_else(|| anyhow!("Cannot find video track with ID `{id}`"))? + .height + .wait() + .read() + .unwrap()) + } + /// Changes the [enabled][1] property of the media track by its ID. /// /// [1]: https://w3.org/TR/mediacapture-streams#track-enabled @@ -924,7 +967,7 @@ impl AudioDeviceModule { /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediadevices-getusermedia /// [2]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia /// [3]: https://w3.org/TR/webrtc/#dom-rtcpeerconnection-ontrack -#[derive(Clone, Debug, Eq, From, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, From, Hash, PartialEq)] pub enum TrackOrigin { Local, Remote(PeerConnectionId), @@ -969,6 +1012,36 @@ pub struct VideoTrack { /// Peers and transceivers sending this [`VideoTrack`]. pub senders: HashMap, HashSet>>, + + /// Tracks changes in video `height` and `width`. + sink: Option, + + /// Video width. + width: Arc>>, + + /// Video height. + height: Arc>>, +} + +/// Tracks changes in video `height` and `width`. +struct VideoFormatSink { + /// Video width. + width: Arc>>, + + /// Video height. + height: Arc>>, +} + +impl OnFrameCallback for VideoFormatSink { + fn on_frame(&mut self, frame: cxx::UniquePtr) { + if self.width.get().is_none() { + self.width.set(RwLock::from(frame.width())).unwrap(); + self.height.set(RwLock::from(frame.height())).unwrap(); + } else { + *self.width.get().unwrap().write().unwrap() = frame.width(); + *self.height.get().unwrap().write().unwrap() = frame.height(); + } + } } impl VideoTrack { @@ -978,15 +1051,39 @@ impl VideoTrack { src: Arc, ) -> anyhow::Result { let id = VideoTrackId(next_id().to_string()); - Ok(Self { + let track_origin = TrackOrigin::Local; + + let width = Arc::new(OnceCell::new()); + let height = Arc::new(OnceCell::new()); + let mut sink = VideoSink::new( + i64::try_from(next_id()).unwrap(), + sys::VideoSinkInterface::create_forwarding(Box::new( + VideoFormatSink { + width: Arc::clone(&width), + height: Arc::clone(&height), + }, + )), + id.clone(), + track_origin, + ); + + let mut res = Self { id: id.clone(), inner: pc.create_video_track(id.into(), &src.inner)?, source: MediaTrackSource::Local(src), kind: api::MediaType::Video, sinks: Vec::new(), senders: HashMap::new(), - track_origin: TrackOrigin::Local, - }) + width, + height, + sink: None, + track_origin, + }; + + res.add_video_sink(&mut sink); + res.sink = Some(sink); + + Ok(res) } /// Wraps the track of the `transceiver.receiver.track()` into a @@ -997,7 +1094,25 @@ impl VideoTrack { ) -> Self { let receiver = transceiver.receiver(); let track = receiver.track(); - Self { + let track_origin = TrackOrigin::Remote(peer.id()); + + let width = Arc::new(OnceCell::new()); + width.set(RwLock::from(0)).unwrap(); + let height = Arc::new(OnceCell::new()); + height.set(RwLock::from(0)).unwrap(); + let mut sink = VideoSink::new( + i64::try_from(next_id()).unwrap(), + sys::VideoSinkInterface::create_forwarding(Box::new( + VideoFormatSink { + width: Arc::clone(&width), + height: Arc::clone(&height), + }, + )), + VideoTrackId(track.id().clone()), + track_origin, + ); + + let mut res = Self { id: VideoTrackId(track.id()), inner: track.try_into().unwrap(), // Safe to unwrap since transceiver is guaranteed to be negotiated @@ -1009,8 +1124,16 @@ impl VideoTrack { kind: api::MediaType::Video, sinks: Vec::new(), senders: HashMap::new(), - track_origin: TrackOrigin::Remote(peer.id()), - } + width, + height, + sink: None, + track_origin, + }; + + res.add_video_sink(&mut sink); + res.sink = Some(sink); + + res } /// Adds the provided [`VideoSink`] to this [`VideoTrack`]. @@ -1043,6 +1166,13 @@ impl VideoTrack { } } +impl Drop for VideoTrack { + fn drop(&mut self) { + let sink = self.sink.take().unwrap(); + self.remove_video_sink(sink); + } +} + impl From<&VideoTrack> for api::MediaStreamTrack { fn from(track: &VideoTrack) -> Self { Self { diff --git a/crates/native/src/video_sink.rs b/crates/native/src/video_sink.rs index ea8aadbc5d..9b31b1281f 100644 --- a/crates/native/src/video_sink.rs +++ b/crates/native/src/video_sink.rs @@ -25,7 +25,7 @@ impl Webrtc { OnFrameCallback(handler), )), track_id: track_id.clone(), - track_origin: track_origin.clone(), + track_origin, }; let mut track = self @@ -44,7 +44,7 @@ impl Webrtc { if let Some(sink) = self.video_sinks.remove(&Id(sink_id)) { if let Some(mut track) = self .video_tracks - .get_mut(&(sink.track_id.clone(), sink.track_origin.clone())) + .get_mut(&(sink.track_id.clone(), sink.track_origin)) { track.remove_video_sink(sink); } @@ -76,6 +76,22 @@ pub struct VideoSink { } impl VideoSink { + /// Creates a new [`VideoSink`]. + #[must_use] + pub fn new( + id: i64, + sink: sys::VideoSinkInterface, + track_id: VideoTrackId, + track_origin: TrackOrigin, + ) -> Self { + Self { + id: Id(id), + inner: sink, + track_id, + track_origin, + } + } + /// Returns an [`Id`] of this [`VideoSink`]. #[must_use] pub fn id(&self) -> Id { diff --git a/example/integration_test/webrtc_test.dart b/example/integration_test/webrtc_test.dart index 474828844c..ce00ba9c09 100644 --- a/example/integration_test/webrtc_test.dart +++ b/example/integration_test/webrtc_test.dart @@ -1012,6 +1012,44 @@ void main() { } }); + testWidgets('Video dimensions', (WidgetTester tester) async { + // iOS simulator does not have camera + if (!Platform.isIOS) { + var caps = DeviceConstraints(); + caps.video.mandatory = DeviceVideoConstraints(); + caps.video.mandatory!.width = 640; + caps.video.mandatory!.height = 480; + + var track = (await getUserMedia(caps))[0]; + + var w = await track.width(); + var h = await track.height(); + + expect(w, equals(640)); + expect(h, equals(480)); + + await track.dispose(); + } + + // Desktop only, since screen sharing is unimplemented on mobile platforms. + if (!Platform.isAndroid && !Platform.isIOS) { + var caps = DisplayConstraints(); + caps.video.mandatory = DeviceVideoConstraints(); + caps.video.mandatory!.width = 320; + caps.video.mandatory!.height = 240; + + var track = (await getDisplayMedia(caps))[0]; + + var w = await track.width(); + var h = await track.height(); + + expect(w, equals(320)); + expect(h, equals(240)); + + await track.dispose(); + } + }); + testWidgets('on_track when peer has transceiver.', (WidgetTester tester) async { var pc1 = await PeerConnection.create(IceTransportType.all, []); diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 1e8ab7deca..6bbe543721 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -36,4 +36,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: be3ab4e988bb308d23906be69146fcbef66fac55 -COCOAPODS: 1.12.1 +COCOAPODS: 1.14.2 diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a335..a6b826db27 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { var stream = await getUserMedia(caps); _mediaDevicesList = await enumerateDevices(); _tracks = stream; - await _localRenderer.setSrcObject( - _tracks!.firstWhere((track) => track.kind() == MediaKind.video)); + await _localRenderer.setSrcObject(_tracks!.firstWhere((track) { + return track.kind() == MediaKind.video; + })); } catch (e) { print(e.toString()); } diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 8f29a3846b..b730283c4d 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -19,4 +19,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 -COCOAPODS: 1.12.1 +COCOAPODS: 1.14.2 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 992ebe81d8..f5084d5fda 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -204,7 +204,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index abdc14e313..8293eb7c28 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =3.1.0 <4.0.0" + dart: ">=3.2.0 <4.0.0" flutter: ">=3.10.0" diff --git a/ios/Classes/controller/MediaStreamTrackController.swift b/ios/Classes/controller/MediaStreamTrackController.swift index 750c52a7ce..4069717202 100644 --- a/ios/Classes/controller/MediaStreamTrackController.swift +++ b/ios/Classes/controller/MediaStreamTrackController.swift @@ -20,6 +20,9 @@ class MediaStreamTrackController { /// Controller of the `eventChannel` management. private var eventController: EventController + /// Indicator whether this controller is disposed. + private var isDisposed: Bool = false + /// Initializes a new `MediaStreamTrackController` for the provided /// `MediaStreamTrackProxy`. init(messenger: FlutterBinaryMessenger, track: MediaStreamTrackProxy) { @@ -54,7 +57,7 @@ class MediaStreamTrackController { /// Handles all the supported Flutter method calls for the controlled /// `MediaStreamTrackProxy`. - func onMethodCall(call: FlutterMethodCall, result: FlutterResult) { + func onMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) { let argsMap = call.arguments as? [String: Any] switch call.method { case "setEnabled": @@ -79,8 +82,23 @@ class MediaStreamTrackController { result(error) } case "dispose": + self.isDisposed = true self.channel.setMethodCallHandler(nil) result(nil) + case "width": + Task { + var width = await self.track.getWidth() + if !self.isDisposed { + result(width) + } + } + case "height": + Task { + var height = await self.track.getHeight() + if !self.isDisposed { + result(height) + } + } default: result(FlutterMethodNotImplemented) } diff --git a/ios/Classes/controller/VideoRendererEventController.swift b/ios/Classes/controller/VideoRendererEventController.swift index 304bafafba..e8bc71a0ec 100644 --- a/ios/Classes/controller/VideoRendererEventController.swift +++ b/ios/Classes/controller/VideoRendererEventController.swift @@ -15,20 +15,24 @@ class VideoRendererEventController: VideoRendererEvent { /// Sends an `onFirstFrameRendered` event to Flutter side. func onFirstFrameRendered(id: Int64) { - self.eventController.sendEvent(data: [ - "event": "onFirstFrameRendered", - "id": id, - ]) + DispatchQueue.main.async { + self.eventController.sendEvent(data: [ + "event": "onFirstFrameRendered", + "id": id, + ]) + } } /// Sends an `onTextureChange` event to Flutter side. func onTextureChange(id: Int64, height: Int32, width: Int32, rotation: Int) { - self.eventController.sendEvent(data: [ - "event": "onTextureChange", - "id": id, - "width": width, - "height": height, - "rotation": rotation, - ]) + DispatchQueue.main.async { + self.eventController.sendEvent(data: [ + "event": "onTextureChange", + "id": id, + "width": width, + "height": height, + "rotation": rotation, + ]) + } } } diff --git a/ios/Classes/proxy/MediaStreamTrackProxy.swift b/ios/Classes/proxy/MediaStreamTrackProxy.swift index ba28fcd10f..bce788a97a 100644 --- a/ios/Classes/proxy/MediaStreamTrackProxy.swift +++ b/ios/Classes/proxy/MediaStreamTrackProxy.swift @@ -20,6 +20,52 @@ class MediaStreamTrackProxy: Equatable { /// Subscribers for `onEnded` callback of this `MediaStreamTrackProxy`. private var onEndedSubscribers: [() -> Void] = [] + /// `RTCVideoRenderer` that tracks video track height and width changes. + private var dimensionsSink: VideoTrackDimensionsSink? + + /// `RTCVideoRenderer` that tracks video track height and width changes. + class VideoTrackDimensionsSink: NSObject, RTCVideoRenderer { + /// Lock that protects width and height. + let cond = NSCondition() + + /// Latest video frame width. + var width: Int? + + /// Latest video frame height. + var height: Int? + + /// Blocks current thread until width and height values are set, should + /// be called before accessing these values to ensure correct + /// synchronization. + func waitForVideoDimensions() { + self.cond.lock() + while self.width == nil || self.height == nil { + self.cond.wait() + } + self.cond.unlock() + } + + /// Fetches width and height values + func renderFrame(_ renderFrame: RTCVideoFrame?) { + let width = Int(renderFrame!.buffer.width) + let height = Int(renderFrame!.buffer.height) + + if height == 0 || width == 0 { + return + } + + self.cond.lock() + + self.width = width + self.height = height + + self.cond.broadcast() + self.cond.unlock() + } + + func setSize(_: CGSize) {} + } + /// Initializes a new `MediaStreamTrackProxy` based on the provided data. init( track: RTCMediaStreamTrack, @@ -32,6 +78,12 @@ class MediaStreamTrackProxy: Equatable { } self.track = track MediaStreamTrackStore.tracks[track.trackId] = self + + if track.kind == "video" { + self.dimensionsSink = VideoTrackDimensionsSink() + let videoTrack = self.track as! RTCVideoTrack + videoTrack.add(self.dimensionsSink!) + } } /// Compares two `MediaStreamTrackProxy`s based on underlying @@ -74,6 +126,32 @@ class MediaStreamTrackProxy: Equatable { } } + /// Returns latest frame width if this is a video track or nil otherwise. + func getWidth() async -> Int? { + if self.dimensionsSink == nil { + return nil + } else { + let task = Task.detached { + self.dimensionsSink!.waitForVideoDimensions() + return self.dimensionsSink!.width! + } + return await task.get() + } + } + + /// Returns latest frame height if this is a video track or nil otherwise. + func getHeight() async -> Int? { + if self.dimensionsSink == nil { + return nil + } else { + let task = Task.detached { + self.dimensionsSink!.waitForVideoDimensions() + return self.dimensionsSink!.height! + } + return await task.get() + } + } + /// Returns `FacingMode` of this `MediaStreamTrackProxy`. /// /// Returns `nil` if it's an audio track. diff --git a/lib/src/api/bridge.g.dart b/lib/src/api/bridge.g.dart index ad4dd6994b..20c57115e9 100644 --- a/lib/src/api/bridge.g.dart +++ b/lib/src/api/bridge.g.dart @@ -252,6 +252,33 @@ abstract class MedeaFlutterWebrtcNative { FlutterRustBridgeTaskConstMeta get kTrackStateConstMeta; + /// Returns the [height] property of the media track by its ID and + /// [`MediaType`]. + /// + /// Blocks until the [height] is initialized. + /// + /// [height]: https://w3.org/TR/mediacapture-streams#dfn-height + Future trackHeight( + {required String trackId, + int? peerId, + required MediaType kind, + dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kTrackHeightConstMeta; + + /// Returns the [width] property of the media track by its ID and [`MediaType`]. + /// + /// Blocks until the [width] is initialized. + /// + /// [width]: https://w3.org/TR/mediacapture-streams#dfn-height + Future trackWidth( + {required String trackId, + int? peerId, + required MediaType kind, + dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kTrackWidthConstMeta; + /// Changes the [enabled][1] property of the [`MediaStreamTrack`] by its ID and /// [`MediaType`]. /// @@ -390,7 +417,7 @@ class ArcRtpTransceiver extends FrbOpaque { } /// Nature and settings of the audio [`MediaStreamTrack`] returned by -/// [`Webrtc::get_users_media()`]. +/// [`Webrtc::get_media()`]. class AudioConstraints { /// Identifier of the device generating the content of the /// [`MediaStreamTrack`]. @@ -707,7 +734,7 @@ class MediaDisplayInfo { /// [MediaStreamConstraints], used to instruct what sort of /// [`MediaStreamTrack`]s to include in the [`MediaStream`] returned by -/// [`Webrtc::get_users_media()`]. +/// [`Webrtc::get_media()`]. /// /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamconstraints class MediaStreamConstraints { @@ -1902,7 +1929,7 @@ enum TrackState { } /// Nature and settings of the video [`MediaStreamTrack`] returned by -/// [`Webrtc::get_users_media()`]. +/// [`Webrtc::get_media()`]. class VideoConstraints { /// Identifier of the device generating the content of the /// [`MediaStreamTrack`]. @@ -2599,6 +2626,56 @@ class MedeaFlutterWebrtcNativeImpl implements MedeaFlutterWebrtcNative { argNames: ["trackId", "peerId", "kind"], ); + Future trackHeight( + {required String trackId, + int? peerId, + required MediaType kind, + dynamic hint}) { + var arg0 = _platform.api2wire_String(trackId); + var arg1 = _platform.api2wire_opt_box_autoadd_u64(peerId); + var arg2 = api2wire_media_type(kind); + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => + _platform.inner.wire_track_height(port_, arg0, arg1, arg2), + parseSuccessData: _wire2api_opt_box_autoadd_i32, + parseErrorData: _wire2api_FrbAnyhowException, + constMeta: kTrackHeightConstMeta, + argValues: [trackId, peerId, kind], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kTrackHeightConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "track_height", + argNames: ["trackId", "peerId", "kind"], + ); + + Future trackWidth( + {required String trackId, + int? peerId, + required MediaType kind, + dynamic hint}) { + var arg0 = _platform.api2wire_String(trackId); + var arg1 = _platform.api2wire_opt_box_autoadd_u64(peerId); + var arg2 = api2wire_media_type(kind); + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => + _platform.inner.wire_track_width(port_, arg0, arg1, arg2), + parseSuccessData: _wire2api_opt_box_autoadd_i32, + parseErrorData: _wire2api_FrbAnyhowException, + constMeta: kTrackWidthConstMeta, + argValues: [trackId, peerId, kind], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kTrackWidthConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "track_width", + argNames: ["trackId", "peerId", "kind"], + ); + Future setTrackEnabled( {required String trackId, int? peerId, @@ -4487,6 +4564,50 @@ class MedeaFlutterWebrtcNativeWire implements FlutterRustBridgeWireBase { void Function( int, ffi.Pointer, ffi.Pointer, int)>(); + void wire_track_height( + int port_, + ffi.Pointer track_id, + ffi.Pointer peer_id, + int kind, + ) { + return _wire_track_height( + port_, + track_id, + peer_id, + kind, + ); + } + + late final _wire_track_heightPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Int64, ffi.Pointer, + ffi.Pointer, ffi.Int32)>>('wire_track_height'); + late final _wire_track_height = _wire_track_heightPtr.asFunction< + void Function( + int, ffi.Pointer, ffi.Pointer, int)>(); + + void wire_track_width( + int port_, + ffi.Pointer track_id, + ffi.Pointer peer_id, + int kind, + ) { + return _wire_track_width( + port_, + track_id, + peer_id, + kind, + ); + } + + late final _wire_track_widthPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Int64, ffi.Pointer, + ffi.Pointer, ffi.Int32)>>('wire_track_width'); + late final _wire_track_width = _wire_track_widthPtr.asFunction< + void Function( + int, ffi.Pointer, ffi.Pointer, int)>(); + void wire_set_track_enabled( int port_, ffi.Pointer track_id, diff --git a/lib/src/platform/native/media_stream_track.dart b/lib/src/platform/native/media_stream_track.dart index 6c75ad7918..fd328f3dec 100644 --- a/lib/src/platform/native/media_stream_track.dart +++ b/lib/src/platform/native/media_stream_track.dart @@ -170,6 +170,16 @@ class _NativeMediaStreamTrackChannel extends NativeMediaStreamTrack { FacingMode? facingMode() { return _facingMode; } + + @override + Future height() async { + return await _chan.invokeMethod('height'); + } + + @override + Future width() async { + return await _chan.invokeMethod('width'); + } } /// FFI-based implementation of a [NativeMediaStreamTrack]. @@ -239,6 +249,23 @@ class _NativeMediaStreamTrackFFI extends NativeMediaStreamTrack { : MediaStreamTrackState.ended; } + @override + FacingMode? facingMode() { + return null; + } + + @override + Future height() async { + return await api!.trackHeight( + trackId: _id, peerId: _peerId, kind: ffi.MediaType.values[_kind.index]); + } + + @override + Future width() async { + return await api!.trackWidth( + trackId: _id, peerId: _peerId, kind: ffi.MediaType.values[_kind.index]); + } + @override Future stop() async { if (!_stopped) { @@ -251,9 +278,4 @@ class _NativeMediaStreamTrackFFI extends NativeMediaStreamTrack { } _stopped = true; } - - @override - FacingMode? facingMode() { - return null; - } } diff --git a/lib/src/platform/track.dart b/lib/src/platform/track.dart index 183b9f1e9a..5f07150eda 100644 --- a/lib/src/platform/track.dart +++ b/lib/src/platform/track.dart @@ -57,4 +57,14 @@ abstract class MediaStreamTrack { /// Returns [FacingMode] of this [MediaStreamTrack]. FacingMode? facingMode(); + + /// Returns [width] of this [MediaStreamTrack]. + /// + /// [width]: https://w3.org/TR/mediacapture-streams#dfn-width + Future width(); + + /// Returns [height] of this [MediaStreamTrack]. + /// + /// [height]: https://w3.org/TR/mediacapture-streams#dfn-height + Future height(); } diff --git a/lib/src/platform/web/media_stream_track.dart b/lib/src/platform/web/media_stream_track.dart index 4d726ed64b..6e9d9b6849 100644 --- a/lib/src/platform/web/media_stream_track.dart +++ b/lib/src/platform/web/media_stream_track.dart @@ -79,4 +79,16 @@ class WebMediaStreamTrack extends MediaStreamTrack { } return null; } + + @override + Future height() { + var settings = jsTrack.getSettings(); + return settings['height']; + } + + @override + Future width() { + var settings = jsTrack.getSettings(); + return settings['width']; + } }