diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6962ca29..6775608e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -248,7 +248,7 @@ jobs: use-tool-cache: true - uses: subosito/flutter-action@v1 with: - flutter-version: 2.5.1 + flutter-version: 2.8.0 - name: Parse Android NDK versions id: ndk-version diff --git a/crates/medea-macro/src/dart_codegen.rs b/crates/medea-macro/src/dart_codegen.rs index ba93d1023..2b26267d1 100644 --- a/crates/medea-macro/src/dart_codegen.rs +++ b/crates/medea-macro/src/dart_codegen.rs @@ -11,26 +11,17 @@ use syn::spanned::Spanned as _; /// Types that can be passed through FFI. #[derive(Debug, Clone, Copy)] pub(crate) enum DartType { - /// Pointer to the Dart `Object`. - /// - /// Represents a [Handle] on the Dart side. - /// - /// [Handle]: https://api.dart.dev/stable/dart-ffi/Handle-class.html - Handle, - - /// [`c_char`] pointer. - /// - /// Represents [Pointer][0] on the Dart side. + /// Type which indicates that function doesn't return anything. /// - /// [0]: https://pub.dev/documentation/ffi/latest/ffi/Utf8-class.html - StringPointer, + /// `void` keyword in Dart. + Void, - /// Pointer to a boxed Dart `Object`. + /// Boolean value. /// - /// Represents [Pointer][0] on the Dart side. + /// Represents [Bool] on the Dart side. /// - /// [0]: https://api.dart.dev/stable/dart-ffi/Pointer-class.html - HandlePointer, + /// [Bool]: https://api.dart.dev/stable/dart-ffi/Bool-class.html + Bool, /// 8-bit integer. /// @@ -39,6 +30,13 @@ pub(crate) enum DartType { /// [Int8]: https://api.dart.dev/stable/dart-ffi/Int8-class.html Int8, + /// 8-bit unsigned integer. + /// + /// Represents [Uint8] on the Dart side. + /// + /// [Uint8]: https://api.dart.dev/stable/dart-ffi/Uint8-class.html + Uint8, + /// 32-bit integer. /// /// Represents [Int32] on the Dart side. @@ -46,6 +44,13 @@ pub(crate) enum DartType { /// [Int32]: https://api.dart.dev/stable/dart-ffi/Int32-class.html Int32, + /// 32-bit unsigned integer. + /// + /// Represents [Uint32] on the Dart side. + /// + /// [Uint32]: https://api.dart.dev/stable/dart-ffi/Uint32-class.html + Uint32, + /// 64-bit integer. /// /// Represents [Int64] on the Dart side. @@ -53,6 +58,20 @@ pub(crate) enum DartType { /// [Int64]: https://api.dart.dev/stable/dart-ffi/Int64-class.html Int64, + /// 64-bit unsigned integer. + /// + /// Represents [Uint64] on the Dart side. + /// + /// [Uint64]: https://api.dart.dev/stable/dart-ffi/Uint64-class.html + Uint64, + + /// Pointer to the Dart `Object`. + /// + /// Represents a [Handle] on the Dart side. + /// + /// [Handle]: https://api.dart.dev/stable/dart-ffi/Handle-class.html + Handle, + /// Pointer to the Rust structure. /// /// Represents [Pointer] on the Dart side. @@ -60,10 +79,19 @@ pub(crate) enum DartType { /// [Pointer]: https://api.dart.dev/stable/dart-ffi/Pointer-class.html Pointer, - /// Type which indicates that function doesn't return anything. + /// Pointer to a boxed Dart `Object`. /// - /// `void` keyword in Dart. - Void, + /// Represents [Pointer][0] on the Dart side. + /// + /// [0]: https://api.dart.dev/stable/dart-ffi/Pointer-class.html + HandlePointer, + + /// [`c_char`] pointer. + /// + /// Represents [Pointer][0] on the Dart side. + /// + /// [0]: https://pub.dev/documentation/ffi/latest/ffi/Utf8-class.html + StringPointer, /// `DartValue` FFI structure which adds ability to cast more complex /// types. @@ -74,14 +102,18 @@ impl DartType { /// Converts this [`DartType`] to the Dart side FFI type. pub(crate) fn to_ffi_type(self) -> &'static str { match self { - Self::Handle => "Handle", - Self::StringPointer => "Pointer", - Self::HandlePointer => "Pointer", + Self::Void => "Void", + Self::Bool => "Bool", Self::Int8 => "Int8", + Self::Uint8 => "Uint8", Self::Int32 => "Int32", + Self::Uint32 => "Uint32", Self::Int64 => "Int64", + Self::Uint64 => "Uint64", + Self::Handle => "Handle", Self::Pointer => "Pointer", - Self::Void => "Void", + Self::HandlePointer => "Pointer", + Self::StringPointer => "Pointer", Self::ForeignValue => "ForeignValue", } } @@ -132,13 +164,17 @@ impl TryFrom for DartType { syn::Error::new(p.span(), "Empty path") })?; match ty.ident.to_string().as_str() { + "bool" => Self::Bool, + "i8" => Self::Int8, + "u8" => Self::Uint8, + "i32" => Self::Int32, + "u32" => Self::Uint32, + "i64" => Self::Int64, + "u64" => Self::Uint64, "Dart_Handle" => Self::Handle, "NonNull" => Self::from_non_null_generic(&ty.arguments)?, "DartValueArg" | "DartValue" => Self::ForeignValue, "DartError" => Self::HandlePointer, - "i32" => Self::Int32, - "i64" => Self::Int64, - "i8" => Self::Int8, _ => { return Err(syn::Error::new( ty.ident.span(), diff --git a/flutter/assets/pkg/medea_jason.js b/flutter/assets/pkg/medea_jason.js index 3f74f0ae4..783832099 100644 --- a/flutter/assets/pkg/medea_jason.js +++ b/flutter/assets/pkg/medea_jason.js @@ -211,27 +211,27 @@ function makeMutClosure(arg0, arg1, dtor, f) { return real; } function __wbg_adapter_34(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2022fc00a0daa960(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1919a0518e3bc15d(arg0, arg1, addHeapObject(arg2)); } function __wbg_adapter_37(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2022fc00a0daa960(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1919a0518e3bc15d(arg0, arg1, addHeapObject(arg2)); } function __wbg_adapter_40(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2022fc00a0daa960(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1919a0518e3bc15d(arg0, arg1, addHeapObject(arg2)); } function __wbg_adapter_43(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2022fc00a0daa960(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1919a0518e3bc15d(arg0, arg1, addHeapObject(arg2)); } function __wbg_adapter_46(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h2022fc00a0daa960(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1919a0518e3bc15d(arg0, arg1, addHeapObject(arg2)); } function __wbg_adapter_49(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6d465b8077092408(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h36627a331f9fe1df(arg0, arg1, addHeapObject(arg2)); } function _assertClass(instance, klass) { @@ -253,7 +253,7 @@ function getArrayU8FromWasm0(ptr, len) { return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); } function __wbg_adapter_342(arg0, arg1, arg2, arg3) { - wasm.wasm_bindgen__convert__closures__invoke2_mut__hb6bcf9e4e0912e25(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); + wasm.wasm_bindgen__convert__closures__invoke2_mut__hd0d132b7c8de1590(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); } /** @@ -2280,28 +2280,8 @@ async function init(input) { var ret = getObject(arg0); return addHeapObject(ret); }; - imports.wbg.__wbg_roomclosereason_new = function(arg0) { - var ret = RoomCloseReason.__wrap(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_string_new = function(arg0, arg1) { - var ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_string_get = function(arg0, arg1) { - const obj = getObject(arg1); - var ret = typeof(obj) === 'string' ? obj : undefined; - var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len0 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len0; - getInt32Memory0()[arg0 / 4 + 0] = ptr0; - }; - imports.wbg.__wbg_inputdeviceinfo_new = function(arg0) { - var ret = InputDeviceInfo.__wrap(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbg_enumeratedevicesexception_new = function(arg0) { - var ret = EnumerateDevicesException.__wrap(arg0); + imports.wbg.__wbg_reconnecthandle_new = function(arg0) { + var ret = ReconnectHandle.__wrap(arg0); return addHeapObject(ret); }; imports.wbg.__wbg_localmediatrack_new = function(arg0) { @@ -2317,14 +2297,26 @@ async function init(input) { var ret = false; return ret; }; + imports.wbg.__wbg_roomclosereason_new = function(arg0) { + var ret = RoomCloseReason.__wrap(arg0); + return addHeapObject(ret); + }; imports.wbg.__wbindgen_number_new = function(arg0) { var ret = arg0; return addHeapObject(ret); }; - imports.wbg.__wbg_reconnecthandle_new = function(arg0) { - var ret = ReconnectHandle.__wrap(arg0); + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + var ret = getStringFromWasm0(arg0, arg1); return addHeapObject(ret); }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + var ret = typeof(obj) === 'string' ? obj : undefined; + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; imports.wbg.__wbg_connectionhandle_new = function(arg0) { var ret = ConnectionHandle.__wrap(arg0); return addHeapObject(ret); @@ -2341,6 +2333,10 @@ async function init(input) { var ret = LocalMediaInitException.__wrap(arg0); return addHeapObject(ret); }; + imports.wbg.__wbg_enumeratedevicesexception_new = function(arg0) { + var ret = EnumerateDevicesException.__wrap(arg0); + return addHeapObject(ret); + }; imports.wbg.__wbg_rpcclientexception_new = function(arg0) { var ret = RpcClientException.__wrap(arg0); return addHeapObject(ret); @@ -2377,6 +2373,10 @@ async function init(input) { var ret = typeof(getObject(arg0)) === 'string'; return ret; }; + imports.wbg.__wbg_inputdeviceinfo_new = function(arg0) { + var ret = InputDeviceInfo.__wrap(arg0); + return addHeapObject(ret); + }; imports.wbg.__wbindgen_number_get = function(arg0, arg1) { const obj = getObject(arg1); var ret = typeof(obj) === 'number' ? obj : undefined; @@ -2401,12 +2401,12 @@ async function init(input) { wasm.__wbindgen_free(arg0, arg1); } }; - imports.wbg.__wbg_getRandomValues_98117e9a7e993920 = function() { return handleError(function (arg0, arg1) { - getObject(arg0).getRandomValues(getObject(arg1)); - }, arguments) }; imports.wbg.__wbg_randomFillSync_64cc7d048f228ca8 = function() { return handleError(function (arg0, arg1, arg2) { getObject(arg0).randomFillSync(getArrayU8FromWasm0(arg1, arg2)); }, arguments) }; + imports.wbg.__wbg_getRandomValues_98117e9a7e993920 = function() { return handleError(function (arg0, arg1) { + getObject(arg0).getRandomValues(getObject(arg1)); + }, arguments) }; imports.wbg.__wbg_process_2f24d6544ea7b200 = function(arg0) { var ret = getObject(arg0).process; return addHeapObject(ret); @@ -2424,6 +2424,10 @@ async function init(input) { var ret = getObject(arg0).node; return addHeapObject(ret); }; + imports.wbg.__wbg_modulerequire_3440a4bcf44437db = function() { return handleError(function (arg0, arg1) { + var ret = module.require(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, arguments) }; imports.wbg.__wbg_crypto_98fc271021c7d2ad = function(arg0) { var ret = getObject(arg0).crypto; return addHeapObject(ret); @@ -2432,10 +2436,6 @@ async function init(input) { var ret = getObject(arg0).msCrypto; return addHeapObject(ret); }; - imports.wbg.__wbg_modulerequire_3440a4bcf44437db = function() { return handleError(function (arg0, arg1) { - var ret = module.require(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }, arguments) }; imports.wbg.__wbg_instanceof_Window_c4b70662a0d2c5ec = function(arg0) { var ret = getObject(arg0) instanceof Window; return ret; @@ -2522,14 +2522,6 @@ async function init(input) { var ret = getObject(arg0).getUserMedia(getObject(arg1)); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_data_9e55e7d79ab13ef1 = function(arg0) { - var ret = getObject(arg0).data; - return addHeapObject(ret); - }; - imports.wbg.__wbg_candidate_d3ec9190d1a6a676 = function(arg0) { - var ret = getObject(arg0).candidate; - return isLikeNone(ret) ? 0 : addHeapObject(ret); - }; imports.wbg.__wbg_track_b0aa7a5107340204 = function(arg0) { var ret = getObject(arg0).track; return addHeapObject(ret); @@ -2538,6 +2530,14 @@ async function init(input) { var ret = getObject(arg0).transceiver; return addHeapObject(ret); }; + imports.wbg.__wbg_data_9e55e7d79ab13ef1 = function(arg0) { + var ret = getObject(arg0).data; + return addHeapObject(ret); + }; + imports.wbg.__wbg_candidate_d3ec9190d1a6a676 = function(arg0) { + var ret = getObject(arg0).candidate; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; imports.wbg.__wbg_iceConnectionState_905bab998d998769 = function(arg0) { var ret = getObject(arg0).iceConnectionState; return addHeapObject(ret); @@ -2854,28 +2854,28 @@ async function init(input) { var ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2327 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 659, __wbg_adapter_34); + imports.wbg.__wbindgen_closure_wrapper419 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 108, __wbg_adapter_34); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2328 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 659, __wbg_adapter_37); + imports.wbg.__wbindgen_closure_wrapper420 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 108, __wbg_adapter_37); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2331 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 659, __wbg_adapter_40); + imports.wbg.__wbindgen_closure_wrapper421 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 108, __wbg_adapter_40); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2333 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 659, __wbg_adapter_43); + imports.wbg.__wbindgen_closure_wrapper423 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 108, __wbg_adapter_43); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2335 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 659, __wbg_adapter_46); + imports.wbg.__wbindgen_closure_wrapper426 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 108, __wbg_adapter_46); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2556 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 760, __wbg_adapter_49); + imports.wbg.__wbindgen_closure_wrapper2482 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 771, __wbg_adapter_49); return addHeapObject(ret); }; diff --git a/flutter/assets/pkg/medea_jason_bg.wasm b/flutter/assets/pkg/medea_jason_bg.wasm index 99ff575d9..8f17c7734 100644 Binary files a/flutter/assets/pkg/medea_jason_bg.wasm and b/flutter/assets/pkg/medea_jason_bg.wasm differ diff --git a/flutter/example/android/app/src/main/AndroidManifest.xml b/flutter/example/android/app/src/main/AndroidManifest.xml index b1280010e..0b3b47a23 100644 --- a/flutter/example/android/app/src/main/AndroidManifest.xml +++ b/flutter/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,15 @@ + + + + + + + + + + diff --git a/flutter/example/lib/call.dart b/flutter/example/lib/call.dart index 4c049b089..6bf672564 100644 --- a/flutter/example/lib/call.dart +++ b/flutter/example/lib/call.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:medea_jason/medea_jason.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; @@ -54,6 +55,9 @@ class Call { _room.onNewConnection((conn) { var remoteMemberId = conn.getRemoteMemberId(); conn.onRemoteTrackAdded((track) async { + if (track.kind() == MediaKind.Audio && !kIsWeb) { + return; + } var sysTrack = track.getTrack(); var remoteStream = await createLocalMediaStream(remoteMemberId); await remoteStream.addTrack(sysTrack); diff --git a/flutter/example/lib/call_route.dart b/flutter/example/lib/call_route.dart index 102b244c8..42b70f816 100644 --- a/flutter/example/lib/call_route.dart +++ b/flutter/example/lib/call_route.dart @@ -32,7 +32,6 @@ class _CallState extends State { var renderer = RTCVideoRenderer(); await renderer.initialize(); renderer.srcObject = stream; - renderer.muted = false; setState(() { _videos.add(RTCVideoView(renderer)); }); diff --git a/flutter/example/pubspec.lock b/flutter/example/pubspec.lock index 59de819f5..65ae8ed96 100644 --- a/flutter/example/pubspec.lock +++ b/flutter/example/pubspec.lock @@ -7,14 +7,14 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.1.2" + version: "3.1.6" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.4" fake_async: dependency: transitive description: @@ -77,7 +77,7 @@ packages: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.2" file: dependency: transitive description: @@ -104,8 +104,8 @@ packages: dependency: transitive description: path: "." - ref: HEAD - resolved-ref: "1cf5f5919eb1a8dde62a8fd84d640078b6ed8e59" + ref: add-missing-apis + resolved-ref: "8a15db8688c0ddb34edb076f9b0bfcb43ec27b1d" url: "https://github.com/instrumentisto/flutter-webrtc.git" source: git version: "0.7.0+hotfix.2" @@ -132,7 +132,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" medea_jason: dependency: "direct main" description: @@ -160,21 +160,35 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.0.8" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.4" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.4" path_provider_platform_interface: dependency: transitive description: @@ -188,35 +202,35 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" pedantic: dependency: "direct dev" description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.11.0" + version: "1.11.1" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.2" process: dependency: transitive description: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.2.3" + version: "4.2.4" sky_engine: dependency: transitive description: flutter @@ -270,7 +284,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.3" typed_data: dependency: transitive description: @@ -284,14 +298,14 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" vm_service: dependency: transitive description: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "7.1.1" + version: "7.3.0" webdriver: dependency: transitive description: @@ -305,7 +319,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.3.1" xdg_directories: dependency: transitive description: @@ -315,4 +329,4 @@ packages: version: "0.2.0" sdks: dart: ">=2.14.0 <3.0.0" - flutter: ">=2.0.0" + flutter: ">=2.5.0" diff --git a/flutter/lib/src/interface/input_device_info.dart b/flutter/lib/src/interface/input_device_info.dart index 93b42ad98..9dd5f2cde 100644 --- a/flutter/lib/src/interface/input_device_info.dart +++ b/flutter/lib/src/interface/input_device_info.dart @@ -29,7 +29,7 @@ abstract class InputDeviceInfo { /// same [`groupId`][1]. /// /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadeviceinfo-groupid - String groupId(); + String? groupId(); /// Drops the associated Rust struct and nulls the local [Pointer] to it. @moveSemantics diff --git a/flutter/lib/src/native/ffi/box_handle.dart b/flutter/lib/src/native/ffi/box_handle.dart index 85cf6b488..b2641174f 100644 --- a/flutter/lib/src/native/ffi/box_handle.dart +++ b/flutter/lib/src/native/ffi/box_handle.dart @@ -6,18 +6,28 @@ typedef _unboxDartHandle_C = Handle Function(Pointer); typedef _unboxDartHandle_Dart = Object Function(Pointer); typedef _boxDartHandle_C = Pointer Function(Handle); typedef _boxDartHandle_Dart = Pointer Function(Object); +typedef _freeBoxedDartHandle_C = Void Function(Pointer); +typedef _freeBoxedDartHandle_Dart = void Function(Pointer); final _boxDartHandle = dl.lookupFunction<_boxDartHandle_C, _boxDartHandle_Dart>('box_dart_handle'); final _unboxDartHandle = dl.lookupFunction<_unboxDartHandle_C, _unboxDartHandle_Dart>( 'unbox_dart_handle'); +final _freeBoxedDartHandle = + dl.lookupFunction<_freeBoxedDartHandle_C, _freeBoxedDartHandle_Dart>( + 'free_boxed_dart_handle'); /// Converts a [`Pointer`] to an [Object] using a Rust trampoline. Object unboxDartHandle(Pointer ptr) { return _unboxDartHandle(ptr); } +/// Frees the provided [`Pointer ptr) { + _freeBoxedDartHandle(ptr); +} + /// Converts an [Object] into a [`Pointer`] using a Rust trampoline. Pointer boxDartHandle(Object ptr) { return _boxDartHandle(ptr); diff --git a/flutter/lib/src/native/ffi/exception.dart b/flutter/lib/src/native/ffi/exception.dart index 68e58924f..b0416a565 100644 --- a/flutter/lib/src/native/ffi/exception.dart +++ b/flutter/lib/src/native/ffi/exception.dart @@ -93,9 +93,9 @@ Object _newInternalException( /// Creates a new [NativeMediaSettingsUpdateException] with the provided error /// [message], error [cause] and [rolledBack] property. Object _newMediaSettingsUpdateException( - Pointer message, Pointer cause, int rolledBack) { - return NativeMediaSettingsUpdateException(message.nativeStringToDartString(), - unboxDartHandle(cause), rolledBack > 0); + Pointer message, Pointer cause, bool rolledBack) { + return NativeMediaSettingsUpdateException( + message.nativeStringToDartString(), unboxDartHandle(cause), rolledBack); } /// Exception thrown when local media acquisition fails. diff --git a/flutter/lib/src/native/ffi/exception.g.dart b/flutter/lib/src/native/ffi/exception.g.dart index 37b21818a..17d85f0f3 100644 --- a/flutter/lib/src/native/ffi/exception.g.dart +++ b/flutter/lib/src/native/ffi/exception.g.dart @@ -33,7 +33,7 @@ void registerFunction( Handle Function(Pointer, ForeignValue, Pointer)>> newInternalException, required Pointer< - NativeFunction, Pointer, Int8)>> + NativeFunction, Pointer, Bool)>> newMediaSettingsUpdateException, }) { dl.lookupFunction< diff --git a/flutter/lib/src/native/ffi/foreign_value.dart b/flutter/lib/src/native/ffi/foreign_value.dart index b5a3c1961..e9f99b9da 100644 --- a/flutter/lib/src/native/ffi/foreign_value.dart +++ b/flutter/lib/src/native/ffi/foreign_value.dart @@ -114,7 +114,7 @@ extension ForeignValuePointer on Pointer { /// Frees Dart side [ForeignValue]. Pointer intoRustOwned() { var out = _boxForeignValue(ref); - free(); + calloc.free(this); return out; } @@ -124,6 +124,9 @@ extension ForeignValuePointer on Pointer { if (ref._tag == 3) { calloc.free(ref._payload.string); } + if (ref._tag == 2) { + freeBoxedDartHandle(ref._payload.handlePtr); + } calloc.free(this); } } diff --git a/flutter/lib/src/native/ffi/list.dart b/flutter/lib/src/native/ffi/list.dart new file mode 100644 index 000000000..1824e4cfe --- /dev/null +++ b/flutter/lib/src/native/ffi/list.dart @@ -0,0 +1,28 @@ +import 'dart:ffi'; + +import 'package:medea_jason/src/native/ffi/foreign_value.dart'; +import 'list.g.dart' as bridge; + +/// Registers functions allowing Rust to create Dart [List]s. +void registerFunctions(DynamicLibrary dl) { + bridge.registerFunction( + dl, + get: Pointer.fromFunction(_get), + length: Pointer.fromFunction(_len, 0), + ); +} + +/// Returns a [Pointer] to a [List] element with the provided [index]. +Pointer _get(List arr, int index) { + final el = arr[index]; + if (el == null) { + return ForeignValue.none().intoRustOwned(); + } else { + return ForeignValue.fromHandle(el).intoRustOwned(); + } +} + +/// Returns length of the provided [List]. +int _len(List arr) { + return arr.length; +} diff --git a/flutter/lib/src/native/ffi/list.g.dart b/flutter/lib/src/native/ffi/list.g.dart new file mode 100644 index 000000000..d98a4fef3 --- /dev/null +++ b/flutter/lib/src/native/ffi/list.g.dart @@ -0,0 +1,15 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:medea_jason/src/native/ffi/foreign_value.dart'; + +void registerFunction( + DynamicLibrary dl, { + required Pointer> get, + required Pointer> length, +}) { + dl.lookupFunction('register_list')( + get, + length, + ); +} diff --git a/flutter/lib/src/native/ffi/map.dart b/flutter/lib/src/native/ffi/map.dart new file mode 100644 index 000000000..5644a78d2 --- /dev/null +++ b/flutter/lib/src/native/ffi/map.dart @@ -0,0 +1,20 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:medea_jason/src/native/ffi/foreign_value.dart'; +import 'map.g.dart' as bridge; + +/// Registers functions allowing Rust to create Dart [Map]s. +void registerFunctions(DynamicLibrary dl) { + bridge.registerFunction(dl, + init: Pointer.fromFunction(_init), set: Pointer.fromFunction(_set)); +} + +/// Returns an empty [Map]. +Object _init() { + return {}; +} + +/// Sets the given [value] under the given [key] in the provided [Map]. +void _set(Map map, Pointer key, ForeignValue value) { + map[key.toDartString()] = value.toDart(); +} diff --git a/flutter/lib/src/native/ffi/map.g.dart b/flutter/lib/src/native/ffi/map.g.dart new file mode 100644 index 000000000..e76ab26a8 --- /dev/null +++ b/flutter/lib/src/native/ffi/map.g.dart @@ -0,0 +1,17 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:medea_jason/src/native/ffi/foreign_value.dart'; + +void registerFunction( + DynamicLibrary dl, { + required Pointer> init, + required Pointer< + NativeFunction, ForeignValue)>> + set, +}) { + dl.lookupFunction('register_map')( + init, + set, + ); +} diff --git a/flutter/lib/src/native/input_device_info.dart b/flutter/lib/src/native/input_device_info.dart index 53539a3f1..6f5ecd491 100644 --- a/flutter/lib/src/native/input_device_info.dart +++ b/flutter/lib/src/native/input_device_info.dart @@ -5,6 +5,7 @@ import 'package:ffi/ffi.dart'; import '../interface/input_device_info.dart'; import '../interface/track_kinds.dart'; import '../util/move_semantic.dart'; +import 'ffi/foreign_value.dart'; import 'ffi/native_string.dart'; import 'ffi/nullable_pointer.dart'; import 'jason.dart'; @@ -18,8 +19,8 @@ typedef _label_Dart = Pointer Function(Pointer); typedef _kind_C = Uint8 Function(Pointer); typedef _kind_Dart = int Function(Pointer); -typedef _nativeGroupId_C = Pointer Function(Pointer); -typedef _nativeGroupId_Dart = Pointer Function(Pointer); +typedef _nativeGroupId_C = ForeignValue Function(Pointer); +typedef _nativeGroupId_Dart = ForeignValue Function(Pointer); typedef _free_C = Void Function(Pointer); typedef _free_Dart = void Function(Pointer); @@ -62,8 +63,8 @@ class NativeInputDeviceInfo extends InputDeviceInfo { } @override - String groupId() { - return _nativeGroupId(ptr.getInnerPtr()).nativeStringToDartString(); + String? groupId() { + return _nativeGroupId(ptr.getInnerPtr()).toDart(); } @moveSemantics diff --git a/flutter/lib/src/native/jason.dart b/flutter/lib/src/native/jason.dart index d7c4bf4cd..6c9b7bd76 100644 --- a/flutter/lib/src/native/jason.dart +++ b/flutter/lib/src/native/jason.dart @@ -9,6 +9,8 @@ import '../interface/room_handle.dart'; import '../util/move_semantic.dart'; import 'ffi/callback.dart' as callback; import 'ffi/function.dart' as function; +import 'ffi/list.dart' as list; +import 'ffi/map.dart' as map; import 'ffi/completer.dart' as completer; import 'ffi/future.dart' as future; import 'ffi/exception.dart' as exceptions; @@ -81,6 +83,8 @@ DynamicLibrary _dl_load() { future.registerFunctions(dl); function.registerFunctions(dl); platform_utils_registerer.registerFunctions(dl); + list.registerFunctions(dl); + map.registerFunctions(dl); executor = Executor(dl); diff --git a/flutter/lib/src/native/platform/constraints.dart b/flutter/lib/src/native/platform/constraints.dart new file mode 100644 index 000000000..254b23a3f --- /dev/null +++ b/flutter/lib/src/native/platform/constraints.dart @@ -0,0 +1,28 @@ +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'dart:ffi'; +import 'constraints.g.dart' as bridge; + +/// Registers functions allowing Rust to operate Dart [MediaStreamConstraints]. +void registerFunctions(DynamicLibrary dl) { + bridge.registerFunction( + dl, + init: Pointer.fromFunction(_new), + audio: Pointer.fromFunction(_setAudio), + video: Pointer.fromFunction(_setVideo), + ); +} + +/// Returns empty [MediaStreamConstraints]. +Object _new() { + return MediaStreamConstraints(); +} + +/// Sets [MediaStreamConstraints.audio] for the provided [cons]. +void _setAudio(MediaStreamConstraints cons, Object val) { + cons.audio = val; +} + +/// Sets [MediaStreamConstraints.video] for the provided [cons]. +void _setVideo(MediaStreamConstraints cons, Object val) { + cons.video = val; +} diff --git a/flutter/lib/src/native/platform/constraints.g.dart b/flutter/lib/src/native/platform/constraints.g.dart new file mode 100644 index 000000000..2d269676c --- /dev/null +++ b/flutter/lib/src/native/platform/constraints.g.dart @@ -0,0 +1,17 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:medea_jason/src/native/ffi/foreign_value.dart'; + +void registerFunction( + DynamicLibrary dl, { + required Pointer> init, + required Pointer> audio, + required Pointer> video, +}) { + dl.lookupFunction('register_constraints')( + init, + audio, + video, + ); +} diff --git a/flutter/lib/src/native/platform/functions_registerer.dart b/flutter/lib/src/native/platform/functions_registerer.dart index aabd36b22..dbcbb0abb 100644 --- a/flutter/lib/src/native/platform/functions_registerer.dart +++ b/flutter/lib/src/native/platform/functions_registerer.dart @@ -5,6 +5,10 @@ import 'media_track.dart' as media_track; import 'peer_connection.dart' as peer_connection; import 'transceiver.dart' as transceiver; import 'ice_servers.dart' as ice_servers; +import 'constraints.dart' as constraints; +import 'media_devices.dart' as media_devices; +import 'transport.dart' as transport; +import 'input_device_info.dart' as input_device_info; import 'ice_candidate.dart' as ice_candidate; /// Registers functions needed for platform utils working. @@ -14,5 +18,9 @@ void registerFunctions(DynamicLibrary dl) { peer_connection.registerFunctions(dl); transceiver.registerFunctions(dl); ice_servers.registerFunctions(dl); + constraints.registerFunctions(dl); + media_devices.registerFunctions(dl); + transport.registerFunctions(dl); + input_device_info.registerFunctions(dl); ice_candidate.registerFunctions(dl); } diff --git a/flutter/lib/src/native/platform/ice_servers.dart b/flutter/lib/src/native/platform/ice_servers.dart index bd9cbdee2..cf448a4c8 100644 --- a/flutter/lib/src/native/platform/ice_servers.dart +++ b/flutter/lib/src/native/platform/ice_servers.dart @@ -23,13 +23,9 @@ Object _new() { void _add(List servers, Pointer url, ForeignValue username, ForeignValue credentials) { var iceServer = {'url': url.toDartString()}; - username = username.toDart(); - if (username is String) { - iceServer['username'] = username as String; - } - credentials = credentials.toDart(); - if (credentials is String) { - iceServer['credentials'] = credentials as String; - } + + iceServer['username'] = username.toDart(); + iceServer['credential'] = credentials.toDart(); + servers.add(iceServer); } diff --git a/flutter/lib/src/native/platform/input_device_info.dart b/flutter/lib/src/native/platform/input_device_info.dart new file mode 100644 index 000000000..23fe297d2 --- /dev/null +++ b/flutter/lib/src/native/platform/input_device_info.dart @@ -0,0 +1,52 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; + +import 'package:medea_jason/src/native/ffi/foreign_value.dart'; + +import 'input_device_info.g.dart' as bridge; + +/// Registers functions allowing Rust to operate Dart [MediaDeviceInfo]. +void registerFunctions(DynamicLibrary dl) { + bridge.registerFunction( + dl, + deviceId: Pointer.fromFunction(_deviceId), + label: Pointer.fromFunction(_label), + groupId: Pointer.fromFunction(_groupId), + kind: Pointer.fromFunction(_kind, 2), + ); +} + +/// Returns [MediaDeviceInfo.deviceId] value. +Pointer _deviceId(MediaDeviceInfo deviceInfo) { + return deviceInfo.deviceId.toNativeUtf8(); +} + +/// Returns [MediaDeviceInfo.label] value. +Pointer _label(MediaDeviceInfo deviceInfo) { + return deviceInfo.label.toNativeUtf8(); +} + +/// Returns [MediaDeviceInfo.groupId] value. +Pointer _groupId(MediaDeviceInfo deviceInfo) { + if (deviceInfo.groupId != null) { + return ForeignValue.fromString(deviceInfo.groupId!).intoRustOwned(); + } else { + return ForeignValue.none().intoRustOwned(); + } +} + +/// Returns [MediaDeviceInfo.kind] value. +int _kind(MediaDeviceInfo deviceInfo) { + // TODO: Refactor flutter-webrtc to use enum instead of String. + if (deviceInfo.kind == 'audioinput') { + return 0; + } else if (deviceInfo.kind == 'videoinput') { + return 1; + } else if (deviceInfo.kind == 'audiooutput') { + return 2; + } else { + // Not supposed to ever happen. + throw StateError('Unknown MediaKind: ${deviceInfo.kind}'); + } +} diff --git a/flutter/lib/src/native/platform/input_device_info.g.dart b/flutter/lib/src/native/platform/input_device_info.g.dart new file mode 100644 index 000000000..74890fe7b --- /dev/null +++ b/flutter/lib/src/native/platform/input_device_info.g.dart @@ -0,0 +1,21 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:medea_jason/src/native/ffi/foreign_value.dart'; + +void registerFunction( + DynamicLibrary dl, { + required Pointer Function(Handle)>> deviceId, + required Pointer> kind, + required Pointer Function(Handle)>> label, + required Pointer> groupId, +}) { + dl.lookupFunction< + Void Function(Pointer, Pointer, Pointer, Pointer), + void Function( + Pointer, Pointer, Pointer, Pointer)>('register_input_device_info')( + deviceId, + kind, + label, + groupId, + ); +} diff --git a/flutter/lib/src/native/platform/media_devices.dart b/flutter/lib/src/native/platform/media_devices.dart new file mode 100644 index 000000000..c7481c2fc --- /dev/null +++ b/flutter/lib/src/native/platform/media_devices.dart @@ -0,0 +1,43 @@ +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'dart:ffi'; +import 'media_devices.g.dart' as bridge; + +/// Registers functions allowing Rust to operate Dart [MediaDevices]. +void registerFunctions(DynamicLibrary dl) { + bridge.registerFunction( + dl, + enumerateDevices: Pointer.fromFunction(_enumerateDevices), + getUserMedia: Pointer.fromFunction(_getUserMedia), + getDisplayMedia: Pointer.fromFunction(_getDisplayMedia), + ); +} + +/// Requests media input access and returns the created [MediaStreamTrack]s. +Object _getUserMedia(MediaStreamConstraints constraints) { + return () async { + var videoConstraints = {}; + if (constraints.video != null && constraints.video['video'] != null) { + videoConstraints = constraints.video['video']; + } + var res = await navigator.mediaDevices.getUserMedia( + { + 'audio': constraints.audio, + 'video': videoConstraints, + }, + ); + // ignore: deprecated_member_use + await res.getMediaTracks(); + return res.getTracks(); + }; +} + +/// Returns all the available media devices. +Object _enumerateDevices() { + return () => navigator.mediaDevices.enumerateDevices(); +} + +/// Starts capturing the contents of a display and returns the created +/// [MediaStreamTrack]s. +Object _getDisplayMedia(Map constraints) { + return () => navigator.mediaDevices.getDisplayMedia(constraints); +} diff --git a/flutter/lib/src/native/platform/media_devices.g.dart b/flutter/lib/src/native/platform/media_devices.g.dart new file mode 100644 index 000000000..63291c1f5 --- /dev/null +++ b/flutter/lib/src/native/platform/media_devices.g.dart @@ -0,0 +1,17 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:medea_jason/src/native/ffi/foreign_value.dart'; + +void registerFunction( + DynamicLibrary dl, { + required Pointer> enumerateDevices, + required Pointer> getUserMedia, + required Pointer> getDisplayMedia, +}) { + dl.lookupFunction('register_media_devices')( + enumerateDevices, + getUserMedia, + getDisplayMedia, + ); +} diff --git a/flutter/lib/src/native/platform/media_track.dart b/flutter/lib/src/native/platform/media_track.dart index bfa053f78..f547f41dc 100644 --- a/flutter/lib/src/native/platform/media_track.dart +++ b/flutter/lib/src/native/platform/media_track.dart @@ -16,7 +16,7 @@ void registerFunctions(DynamicLibrary dl) { height: Pointer.fromFunction(_height), width: Pointer.fromFunction(_width), setEnabled: Pointer.fromFunction(_setEnabled), - enabled: Pointer.fromFunction(_enabled, 0), + enabled: Pointer.fromFunction(_enabled, false), stop: Pointer.fromFunction(_stop), onEnded: Pointer.fromFunction(_onEnded), readyState: Pointer.fromFunction(_readyState, 0), @@ -47,8 +47,7 @@ void _onEnded(MediaStreamTrack track, Function f) { /// Returns device ID of the provided [MediaStreamTrack]. Pointer _deviceId(MediaStreamTrack track) { - // TODO: Correct implementation requires flutter_webrtc-side fixes. - return _id(track); + return track.deviceId()!.toNativeUtf8(); } int _readyState(MediaStreamTrack track) { @@ -75,8 +74,8 @@ Pointer _width(MediaStreamTrack track) { } /// Sets [MediaStreamTrack.enabled] state of the provided [MediaStreamTrack]. -void _setEnabled(MediaStreamTrack track, int enabled) { - track.enabled = enabled == 1; +void _setEnabled(MediaStreamTrack track, bool enabled) { + track.enabled = enabled; } /// Stops provided [MediaStreamTrack]. @@ -84,7 +83,7 @@ void _stop(MediaStreamTrack track) { track.stop(); } -/// Returns `1` if the provided [MediaStreamTrack] is enabled and `0` otherwise. -int _enabled(MediaStreamTrack track) { - return track.enabled ? 1 : 0; +/// Indicates whether the provided [MediaStreamTrack] is enabled. +bool _enabled(MediaStreamTrack track) { + return track.enabled; } diff --git a/flutter/lib/src/native/platform/media_track.g.dart b/flutter/lib/src/native/platform/media_track.g.dart index 956f10c8d..9903ab0ac 100644 --- a/flutter/lib/src/native/platform/media_track.g.dart +++ b/flutter/lib/src/native/platform/media_track.g.dart @@ -6,14 +6,14 @@ void registerFunction( DynamicLibrary dl, { required Pointer Function(Handle)>> id, required Pointer Function(Handle)>> deviceId, + required Pointer> kind, required Pointer> facingMode, required Pointer> height, required Pointer> width, - required Pointer> setEnabled, - required Pointer> stop, - required Pointer> enabled, - required Pointer> kind, + required Pointer> enabled, + required Pointer> setEnabled, required Pointer> readyState, + required Pointer> stop, required Pointer> onEnded, }) { dl.lookupFunction< @@ -33,14 +33,14 @@ void registerFunction( Pointer)>('register_media_stream_track')( id, deviceId, + kind, facingMode, height, width, - setEnabled, - stop, enabled, - kind, + setEnabled, readyState, + stop, onEnded, ); } diff --git a/flutter/lib/src/native/platform/peer_connection.dart b/flutter/lib/src/native/platform/peer_connection.dart index 171779336..f8ce87f12 100644 --- a/flutter/lib/src/native/platform/peer_connection.dart +++ b/flutter/lib/src/native/platform/peer_connection.dart @@ -26,6 +26,7 @@ void registerFunctions(DynamicLibrary dl) { createAnswer: Pointer.fromFunction(_createAnswer), getTransceiverByMid: Pointer.fromFunction(_getTransceiverByMid), onConnectionStateChange: Pointer.fromFunction(_onConnectionStateChange), + close: Pointer.fromFunction(_close), ); } @@ -33,16 +34,17 @@ void registerFunctions(DynamicLibrary dl) { /// /// Returns [Future] which will be resolved into created [RTCRtpTransceiver]. Object _addTransceiver(RTCPeerConnection peer, int kind, int direction) { - return peer.addTransceiver( - kind: RTCRtpMediaType.values[kind], - init: RTCRtpTransceiverInit(direction: TransceiverDirection.SendRecv), - ); + return () => peer.addTransceiver( + kind: RTCRtpMediaType.values[kind], + init: RTCRtpTransceiverInit( + direction: TransceiverDirection.values[direction]), + ); } /// Returns newly created [RTCPeerConnection] with a provided `iceServers` /// [List]. Object _newPeer(Object iceServers) { - return createPeerConnection( + return () => createPeerConnection( {'iceServers': iceServers, 'sdpSemantics': 'unified-plan'}); } @@ -81,38 +83,38 @@ void _onConnectionStateChange(RTCPeerConnection conn, Function f) { /// Lookups [RTCRtpTransceiver] in the provided [RTCPeerConnection] by the /// provided [String]. Object _getTransceiverByMid(RTCPeerConnection peer, Pointer mid) { - return peer.getTransceivers().then((transceivers) { - var mMid = mid.toDartString(); - for (var transceiver in transceivers) { - if (transceiver.mid == mMid) { - return transceiver; - } - } - }); + return () => peer.getTransceivers().then((transceivers) { + var mMid = mid.toDartString(); + for (var transceiver in transceivers) { + if (transceiver.mid == mMid) { + return transceiver; + } + } + }); } /// Sets remote SDP offer in the provided [RTCPeerConnection]. Object _setRemoteDescription( RTCPeerConnection conn, Pointer type, Pointer sdp) { var desc = RTCSessionDescription(sdp.toDartString(), type.toDartString()); - return conn.setRemoteDescription(desc); + return () => conn.setRemoteDescription(desc); } /// Sets local SDP offer in the provided [RTCPeerConnection]. Object _setLocalDescription( RTCPeerConnection conn, Pointer type, Pointer sdp) { - return conn.setLocalDescription( + return () => conn.setLocalDescription( RTCSessionDescription(sdp.toDartString(), type.toDartString())); } /// Creates new SDP offer for the provided [RTCPeerConnection]. Object _createOffer(RTCPeerConnection conn) { - return conn.createOffer({}).then((val) => val.sdp); + return () => conn.createOffer({}).then((val) => val.sdp); } /// Creates new SDP answer for the provided [RTCPeerConnection]. Object _createAnswer(RTCPeerConnection conn) { - return conn.createAnswer({}).then((val) => val.sdp); + return () => conn.createAnswer({}).then((val) => val.sdp); } /// Restarts ICE on the provided [RTCPeerConnection]. @@ -122,7 +124,7 @@ void _restartIce(RTCPeerConnection conn) { /// Adds provided [RTCIceCandidate] to the provided [RTCPeerConnection]. Object _addIceCandidate(RTCPeerConnection conn, RTCIceCandidate candidate) { - return conn.addCandidate(candidate); + return () => conn.addCandidate(candidate); } /// Returns current [RTCPeerConnection.connectionState] of the provided @@ -147,10 +149,16 @@ int _iceConnectionState(RTCPeerConnection conn) { /// Rollbacks local SDP offer of the provided [RTCPeerConnection]. Object _rollback(RTCPeerConnection conn) { - return conn.setLocalDescription(RTCSessionDescription(null, 'rollback')); + return () => + conn.setLocalDescription(RTCSessionDescription(null, 'rollback')); } /// Returns all [RTCRtpTransceiver]s of the provided [RTCPeerConnection]. Object getTransceivers(RTCPeerConnection conn) { - return conn.getTransceivers(); + return () => conn.getTransceivers(); +} + +/// Closes the provided [RTCPeerConnection]. +void _close(RTCPeerConnection conn) { + conn.dispose(); } diff --git a/flutter/lib/src/native/platform/peer_connection.g.dart b/flutter/lib/src/native/platform/peer_connection.g.dart index 22dfbf146..e492554b6 100644 --- a/flutter/lib/src/native/platform/peer_connection.g.dart +++ b/flutter/lib/src/native/platform/peer_connection.g.dart @@ -30,6 +30,7 @@ void registerFunction( required Pointer< NativeFunction, Pointer)>> setRemoteDescription, + required Pointer> close, }) { dl.lookupFunction< Void Function( @@ -48,6 +49,7 @@ void registerFunction( Pointer, Pointer, Pointer, + Pointer, Pointer), void Function( Pointer, @@ -65,6 +67,7 @@ void registerFunction( Pointer, Pointer, Pointer, + Pointer, Pointer)>('register_peer_connection')( iceConnectionState, onConnectionStateChange, @@ -82,5 +85,6 @@ void registerFunction( createAnswer, setLocalDescription, setRemoteDescription, + close, ); } diff --git a/flutter/lib/src/native/platform/transceiver.dart b/flutter/lib/src/native/platform/transceiver.dart index 4dd292ac7..7e3082070 100644 --- a/flutter/lib/src/native/platform/transceiver.dart +++ b/flutter/lib/src/native/platform/transceiver.dart @@ -9,14 +9,14 @@ import 'transceiver.g.dart' as bridge; void registerFunctions(DynamicLibrary dl) { bridge.registerFunction( dl, - getCurrentDirection: Pointer.fromFunction(_getCurrentDirection), + getDirection: Pointer.fromFunction(_getDirection), replaceTrack: Pointer.fromFunction(_replaceSendTrack), getSendTrack: Pointer.fromFunction(_getSendTrack), setSendTrackEnabled: Pointer.fromFunction(_setSendTrackEnabled), dropSender: Pointer.fromFunction(_dropSender), - isStopped: Pointer.fromFunction(_isStopped), + isStopped: Pointer.fromFunction(_isStopped, true), mid: Pointer.fromFunction(_mid), - hasSendTrack: Pointer.fromFunction(_hasSendTrack, 0), + hasSendTrack: Pointer.fromFunction(_hasSendTrack, false), setDirection: Pointer.fromFunction(_setDirection), ); } @@ -24,18 +24,18 @@ void registerFunctions(DynamicLibrary dl) { /// Sets [TransceiverDirection] of the provided [RTCRtpTransceiver] to the /// provided one. Object _setDirection(RTCRtpTransceiver transceiver, int direction) { - return transceiver.setDirection(TransceiverDirection.values[direction]); + return () => transceiver.setDirection(TransceiverDirection.values[direction]); } /// Returns current [TransceiverDirection] of the provided [RTCRtpTransceiver]. -Object _getCurrentDirection(RTCRtpTransceiver transceiver) { - return transceiver.getCurrentDirection().then((d) => d?.index); +Object _getDirection(RTCRtpTransceiver transceiver) { + return () => transceiver.getDirection().then((d) => d.index); } /// Returns current MID of the provided [RTCRtpTransceiver]. Pointer _mid(RTCRtpTransceiver transceiver) { - if (transceiver.mid.isNotEmpty) { - return ForeignValue.fromString(transceiver.mid).intoRustOwned(); + if (transceiver.mid != null) { + return ForeignValue.fromString(transceiver.mid!).intoRustOwned(); } else { return ForeignValue.none().intoRustOwned(); } @@ -51,29 +51,24 @@ Pointer _getSendTrack(RTCRtpTransceiver transceiver) { } } -/// Returns `1` if provided [RTCRtpTransceiver]'s [RTCRtpTransceiver.sender] -/// has some [MediaStreamTrack]. -int _hasSendTrack(RTCRtpTransceiver transceiver) { - if (transceiver.sender.track == null) { - return 0; - } else { - return 1; - } +/// Indicates whether the provided [RTCRtpTransceiver]'s +/// [RTCRtpTransceiver.sender] has some [MediaStreamTrack]. +bool _hasSendTrack(RTCRtpTransceiver transceiver) { + return transceiver.sender.track != null; } /// Replaces [RTCRtpTransceiver.sender]'s [MediaStreamTrack] of the provided /// [RTCRtpTransceiver] with a provided [MediaStreamTrack]. Object _replaceSendTrack( - RTCRtpTransceiver transceiver, MediaStreamTrack track) async { - await transceiver.sender.setTrack(track); - return ForeignValue.none().ref; + RTCRtpTransceiver transceiver, MediaStreamTrack track) { + return () => transceiver.sender.setTrack(track); } /// Sets [MediaStreamTrack.enabled] status in the [RTCRtpTransceiver.sender] of /// the provided [RTCRtpTransceiver]. -void _setSendTrackEnabled(RTCRtpTransceiver transceiver, int enabled) { +void _setSendTrackEnabled(RTCRtpTransceiver transceiver, bool enabled) { if (transceiver.sender.track != null) { - transceiver.sender.track!.enabled = enabled == 1; + transceiver.sender.track!.enabled = enabled; } } @@ -87,15 +82,8 @@ Object _dropSender(RTCRtpTransceiver transceiver) { } } -/// Returns `1` if [RTCRtpTransceiver.sender]'s [MediaStreamTrack] is stopped. -/// -/// Returns [ForeignValue.none] if [RTCRtpTransceiver.sender] is `null`. -Pointer _isStopped(RTCRtpTransceiver transceiver) { - if (transceiver.sender.track != null && - transceiver.sender.track!.muted != null) { - return ForeignValue.fromInt(transceiver.sender.track!.muted! ? 1 : 0) - .intoRustOwned(); - } else { - return ForeignValue.none().intoRustOwned(); - } +/// Indicates whether the [RTCRtpTransceiver.sender]'s [MediaStreamTrack] is +/// stopped. +bool _isStopped(RTCRtpTransceiver transceiver) { + return transceiver.stoped; } diff --git a/flutter/lib/src/native/platform/transceiver.g.dart b/flutter/lib/src/native/platform/transceiver.g.dart index 60be4e263..231cbb464 100644 --- a/flutter/lib/src/native/platform/transceiver.g.dart +++ b/flutter/lib/src/native/platform/transceiver.g.dart @@ -4,16 +4,16 @@ import 'package:medea_jason/src/native/ffi/foreign_value.dart'; void registerFunction( DynamicLibrary dl, { - required Pointer> getCurrentDirection, + required Pointer> getDirection, required Pointer> getSendTrack, required Pointer> replaceTrack, required Pointer> dropSender, - required Pointer> isStopped, - required Pointer> + required Pointer> isStopped, + required Pointer> setSendTrackEnabled, required Pointer> mid, - required Pointer> hasSendTrack, + required Pointer> hasSendTrack, required Pointer> setDirection, }) { dl.lookupFunction< @@ -21,7 +21,7 @@ void registerFunction( Pointer, Pointer, Pointer), void Function(Pointer, Pointer, Pointer, Pointer, Pointer, Pointer, Pointer, Pointer, Pointer)>('register_transceiver')( - getCurrentDirection, + getDirection, getSendTrack, replaceTrack, dropSender, diff --git a/flutter/lib/src/native/platform/transport.dart b/flutter/lib/src/native/platform/transport.dart new file mode 100644 index 000000000..1bba823cf --- /dev/null +++ b/flutter/lib/src/native/platform/transport.dart @@ -0,0 +1,49 @@ +import 'dart:ffi'; +import 'dart:io'; + +import 'package:ffi/ffi.dart'; + +import 'transport.g.dart' as bridge; + +/// Registers functions allowing Rust to operate Dart [WebSocket]s. +void registerFunctions(DynamicLibrary dl) { + bridge.registerFunction( + dl, + connect: Pointer.fromFunction(_connect), + send: Pointer.fromFunction(_send), + close: Pointer.fromFunction(_close), + ); +} + +/// Connects to the provided [addr] and returns [WebSocket] for it. +/// +/// Subscribes to the created [WebSocket] messages with the given [onMessage] +/// and [onClose] callbacks. +Object _connect(Pointer addr, Function onMessage, Function onClose) { + return () async { + var ws = await WebSocket.connect(addr.toDartString()); + ws.listen( + (msg) { + if (msg is String) { + onMessage(msg); + } + }, + onDone: () { + onClose(); + }, + cancelOnError: true, + ); + return ws; + }; +} + +/// Sends the provided [message] to the provided [WebSocket]. +void _send(WebSocket ws, Pointer message) { + ws.add(message.toDartString()); +} + +/// Closes the provided [WebSocket] connection with the provided +/// [closeCode] and [closeMsg]. +void _close(WebSocket ws, int closeCode, Pointer closeMsg) { + ws.close(closeCode, closeMsg.toDartString()); +} diff --git a/flutter/lib/src/native/platform/transport.g.dart b/flutter/lib/src/native/platform/transport.g.dart new file mode 100644 index 000000000..2d3183e93 --- /dev/null +++ b/flutter/lib/src/native/platform/transport.g.dart @@ -0,0 +1,20 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:medea_jason/src/native/ffi/foreign_value.dart'; + +void registerFunction( + DynamicLibrary dl, { + required Pointer< + NativeFunction, Handle, Handle)>> + connect, + required Pointer)>> send, + required Pointer)>> + close, +}) { + dl.lookupFunction('register_transport')( + connect, + send, + close, + ); +} diff --git a/flutter/lib/src/web/input_device_info.dart b/flutter/lib/src/web/input_device_info.dart index e341a15e8..8aab49342 100644 --- a/flutter/lib/src/web/input_device_info.dart +++ b/flutter/lib/src/web/input_device_info.dart @@ -25,7 +25,7 @@ class WebInputDeviceInfo extends InputDeviceInfo { } @override - String groupId() { + String? groupId() { return fallibleFunction(() => obj.group_id()); } diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index c38aaadf6..3be77fccb 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -13,7 +13,9 @@ dependencies: sdk: flutter ffi: ^1.0.0 flutter_webrtc: - git: https://github.com/instrumentisto/flutter-webrtc.git + git: + url: https://github.com/instrumentisto/flutter-webrtc.git + ref: add-missing-apis js: ^0.6.3 dev_dependencies: diff --git a/src/api/dart/input_device_info.rs b/src/api/dart/input_device_info.rs index 0303b6c08..9e5445d8f 100644 --- a/src/api/dart/input_device_info.rs +++ b/src/api/dart/input_device_info.rs @@ -2,7 +2,7 @@ use std::{os::raw::c_char, ptr}; use super::{utils::string_into_c_str, ForeignClass}; -use crate::media::MediaKind; +use crate::{api::DartValueArg, media::MediaKind}; #[cfg(feature = "mockable")] pub use self::mock::InputDeviceInfo; @@ -53,8 +53,8 @@ pub unsafe extern "C" fn InputDeviceInfo__label( #[no_mangle] pub unsafe extern "C" fn InputDeviceInfo__group_id( this: ptr::NonNull, -) -> ptr::NonNull { - string_into_c_str(this.as_ref().group_id()) +) -> DartValueArg> { + DartValueArg::from(this.as_ref().group_id()) } /// Frees the data behind the provided pointer. @@ -67,7 +67,7 @@ pub unsafe extern "C" fn InputDeviceInfo__group_id( pub unsafe extern "C" fn InputDeviceInfo__free( this: ptr::NonNull, ) { - let _ = InputDeviceInfo::from_ptr(this); + drop(InputDeviceInfo::from_ptr(this)); } #[cfg(feature = "mockable")] @@ -89,8 +89,8 @@ mod mock { String::from("InputDeviceInfo.label") } - pub fn group_id(&self) -> String { - String::from("InputDeviceInfo.group_id") + pub fn group_id(&self) -> Option { + Some(String::from("InputDeviceInfo.group_id")) } } } diff --git a/src/api/dart/jason.rs b/src/api/dart/jason.rs index 46fe06e20..f90bd8727 100644 --- a/src/api/dart/jason.rs +++ b/src/api/dart/jason.rs @@ -54,7 +54,8 @@ pub unsafe extern "C" fn Jason__close_room( /// once for the same pointer is equivalent to double free. #[no_mangle] pub unsafe extern "C" fn Jason__free(this: ptr::NonNull) { - drop(Jason::from_ptr(this)); + let jason = Jason::from_ptr(this); + jason.dispose(); } #[cfg(feature = "mockable")] @@ -78,5 +79,7 @@ mod mock { } pub fn close_room(&self, _: RoomHandle) {} + + pub fn dispose(self) {} } } diff --git a/src/api/dart/local_media_track.rs b/src/api/dart/local_media_track.rs index 05dfbe16c..8946815ab 100644 --- a/src/api/dart/local_media_track.rs +++ b/src/api/dart/local_media_track.rs @@ -1,5 +1,7 @@ use std::ptr; +use dart_sys::Dart_Handle; + use super::ForeignClass; use crate::media::{MediaKind, MediaSourceKind}; @@ -11,6 +13,17 @@ pub use crate::media::track::local::LocalMediaTrack; impl ForeignClass for LocalMediaTrack {} +/// Returns a [`Dart_Handle`] to the underlying [`MediaStreamTrack`] of this +/// [`LocalMediaTrack`]. +/// +/// [`MediaStreamTrack`]: crate::platform::MediaStreamTrack +#[no_mangle] +pub unsafe extern "C" fn LocalMediaTrack__get_track( + this: ptr::NonNull, +) -> Dart_Handle { + this.as_ref().get_track().handle() +} + /// Returns a [`MediaKind::Audio`] if this [`LocalMediaTrack`] represents an /// audio track, or a [`MediaKind::Video`] if it represents a video track. /// @@ -53,9 +66,12 @@ pub unsafe extern "C" fn LocalMediaTrack__free( #[cfg(feature = "mockable")] mod mock { - use crate::media::{ - track::local::LocalMediaTrack as CoreLocalMediaTrack, MediaKind, - MediaSourceKind, + use crate::{ + media::{ + track::local::LocalMediaTrack as CoreLocalMediaTrack, MediaKind, + MediaSourceKind, + }, + platform, }; pub struct LocalMediaTrack(pub u8); @@ -75,6 +91,8 @@ mod mock { MediaSourceKind::Display } - // pub fn get_track(&self) -> sys::MediaStreamTrack + pub fn get_track(&self) -> platform::MediaStreamTrack { + unreachable!() + } } } diff --git a/src/api/dart/mod.rs b/src/api/dart/mod.rs index 388877013..17fc65e6c 100644 --- a/src/api/dart/mod.rs +++ b/src/api/dart/mod.rs @@ -31,7 +31,13 @@ use libc::c_char; use crate::{ api::dart::utils::{DartError, PtrArray}, media::{FacingMode, MediaKind, MediaSourceKind}, - platform::utils::handle::DartHandle, + platform::utils::{ + dart_api::{ + Dart_DeletePersistentHandle_DL_Trampolined, + Dart_NewPersistentHandle_DL_Trampolined, + }, + handle::DartHandle, + }, }; pub use self::{ @@ -575,7 +581,16 @@ impl TryFrom for MediaKind { pub unsafe extern "C" fn unbox_dart_handle( val: ptr::NonNull, ) -> Dart_Handle { - *Box::from_raw(val.as_ptr()) + *val.as_ptr() +} + +/// Frees the provided [`ptr::NonNull`] pointer to a [`Dart_Handle`]. +#[no_mangle] +pub unsafe extern "C" fn free_boxed_dart_handle( + val: ptr::NonNull, +) { + let handle = Box::from_raw(val.as_ptr()); + Dart_DeletePersistentHandle_DL_Trampolined(*handle); } /// Returns a pointer to a boxed [`Dart_Handle`] created from the provided @@ -584,7 +599,8 @@ pub unsafe extern "C" fn unbox_dart_handle( pub unsafe extern "C" fn box_dart_handle( val: Dart_Handle, ) -> ptr::NonNull { - ptr::NonNull::from(Box::leak(Box::new(val))) + let persisted = Dart_NewPersistentHandle_DL_Trampolined(val); + ptr::NonNull::from(Box::leak(Box::new(persisted))) } /// Returns a boxed pointer to the provided [`DartValue`]. diff --git a/src/api/dart/remote_media_track.rs b/src/api/dart/remote_media_track.rs index edf9eba3e..3d20a7ee6 100644 --- a/src/api/dart/remote_media_track.rs +++ b/src/api/dart/remote_media_track.rs @@ -16,6 +16,17 @@ pub use crate::media::track::remote::Track as RemoteMediaTrack; impl ForeignClass for RemoteMediaTrack {} +/// Returns a [`Dart_Handle`] to the underlying [`MediaStreamTrack`] of this +/// [`RemoteMediaTrack`]. +/// +/// [`MediaStreamTrack`]: platform::MediaStreamTrack +#[no_mangle] +pub unsafe extern "C" fn RemoteMediaTrack__get_track( + this: ptr::NonNull, +) -> Dart_Handle { + this.as_ref().get_track().handle() +} + /// Sets callback, invoked when this [`RemoteMediaTrack`] is enabled. #[no_mangle] pub unsafe extern "C" fn RemoteMediaTrack__on_enabled( @@ -141,8 +152,6 @@ mod mock { false } - // pub fn get_track(&self) -> sys::MediaStreamTrack - pub fn on_enabled(&self, cb: platform::Function<()>) { cb.call0(); } @@ -162,5 +171,9 @@ mod mock { pub fn on_stopped(&self, cb: platform::Function<()>) { cb.call0(); } + + pub fn get_track(&self) -> platform::MediaStreamTrack { + unreachable!() + } } } diff --git a/src/api/dart/room_handle.rs b/src/api/dart/room_handle.rs index 1067df2c1..6b05ec09e 100644 --- a/src/api/dart/room_handle.rs +++ b/src/api/dart/room_handle.rs @@ -119,7 +119,7 @@ pub unsafe extern "C" fn RoomHandle__unmute_audio( let this = this.as_ref().clone(); async move { - this.mute_audio().await?; + this.unmute_audio().await?; Ok(()) } .into_dart_future() diff --git a/src/api/dart/utils/err.rs b/src/api/dart/utils/err.rs index d5e8d0d83..7f4509b93 100644 --- a/src/api/dart/utils/err.rs +++ b/src/api/dart/utils/err.rs @@ -99,7 +99,7 @@ mod exception { pub fn new_media_settings_update_exception( message: ptr::NonNull, cause: DartError, - rolled_back: i8, + rolled_back: bool, ) -> Dart_Handle; } } @@ -264,7 +264,7 @@ impl From for DartError { Self::new(exception::new_media_settings_update_exception( string_into_c_str(err.message()), err.cause(), - err.rolled_back() as i8, + err.rolled_back(), )) } } diff --git a/src/api/wasm/input_device_info.rs b/src/api/wasm/input_device_info.rs index 350908251..c3f13dd23 100644 --- a/src/api/wasm/input_device_info.rs +++ b/src/api/wasm/input_device_info.rs @@ -50,7 +50,7 @@ impl InputDeviceInfo { /// /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediadeviceinfo-groupid #[must_use] - pub fn group_id(&self) -> String { + pub fn group_id(&self) -> Option { self.0.group_id() } } diff --git a/src/api/wasm/jason.rs b/src/api/wasm/jason.rs index 3588b7d30..372629ef9 100644 --- a/src/api/wasm/jason.rs +++ b/src/api/wasm/jason.rs @@ -8,7 +8,6 @@ use wasm_bindgen::prelude::*; use crate::{ api::{MediaManagerHandle, RoomHandle}, jason, - platform::{init_logger, set_panic_hook}, }; /// General JS side library interface. @@ -25,9 +24,6 @@ impl Jason { #[must_use] #[wasm_bindgen(constructor)] pub fn new() -> Self { - set_panic_hook(); - init_logger(); - Self(jason::Jason::new()) } diff --git a/src/jason.rs b/src/jason.rs index 9eea07f81..f1a5fb50e 100644 --- a/src/jason.rs +++ b/src/jason.rs @@ -40,6 +40,9 @@ impl Jason { /// Instantiates a new [`Jason`] interface to interact with this library. #[must_use] pub fn new() -> Self { + platform::set_panic_hook(); + platform::init_logger(); + Self::with_rpc_client(Rc::new(WebSocketRpcClient::new(Box::new( |url| { Box::pin(async move { diff --git a/src/media/constraints.rs b/src/media/constraints.rs index 2cda6322b..4a290a71c 100644 --- a/src/media/constraints.rs +++ b/src/media/constraints.rs @@ -5,6 +5,7 @@ use std::{ rc::Rc, }; +use derive_more::Display; use medea_client_api_proto::{ AudioSettings as ProtoAudioConstraints, MediaSourceKind, MediaType as ProtoTrackConstraints, MediaType, VideoSettings, @@ -24,19 +25,23 @@ use crate::{ /// Representation of a [VideoFacingModeEnum][1]. /// /// [1]: https://w3.org/TR/mediacapture-streams#dom-videofacingmodeenum -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Display, Eq, PartialEq)] #[repr(u8)] pub enum FacingMode { /// Facing towards a user (a self-view camera). + #[display(fmt = "user")] User = 0, /// Facing away from a user (viewing an environment). + #[display(fmt = "environment")] Environment = 1, /// Facing to the left of a user. + #[display(fmt = "left")] Left = 2, /// Facing to the right of a user. + #[display(fmt = "right")] Right = 3, } @@ -931,7 +936,10 @@ impl AudioTrackConstraints { ) -> bool { let track = track.as_ref(); satisfies_track(track, MediaKind::Audio) - && ConstrainString::satisfies(&self.device_id, &track.device_id()) + && ConstrainString::satisfies( + &self.device_id, + &Some(track.device_id()), + ) // TODO returns Result } @@ -1157,7 +1165,10 @@ impl DeviceVideoTrackConstraints { ) -> bool { let track = track.as_ref(); satisfies_track(track, MediaKind::Video) - && ConstrainString::satisfies(&self.device_id, &track.device_id()) + && ConstrainString::satisfies( + &self.device_id, + &Some(track.device_id()), + ) && ConstrainString::satisfies( &self.facing_mode, &track.facing_mode(), diff --git a/src/platform/dart/constraints.rs b/src/platform/dart/constraints.rs index 596d716ef..35d37d072 100644 --- a/src/platform/dart/constraints.rs +++ b/src/platform/dart/constraints.rs @@ -1,78 +1,249 @@ -//! Media tracks and streams constraints functionality. +//! Representations of [MediaTrackConstraints][0] and +//! [MediaStreamConstraints][1]. +//! +//! [0]: https://w3.org/TR/mediacapture-streams#media-track-constraints +//! [1]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints -use derive_more::AsRef; +use dart_sys::Dart_Handle; +use derive_more::From; +use medea_macro::dart_bridge; -use crate::media::{ - AudioTrackConstraints, DeviceVideoTrackConstraints, - DisplayVideoTrackConstraints, +use crate::{ + media::{ + constraints::{ConstrainString, ConstrainU32}, + AudioTrackConstraints, DeviceVideoTrackConstraints, + DisplayVideoTrackConstraints, + }, + platform::dart::utils::{handle::DartHandle, map::DartMap}, }; -/// [MediaStreamConstraints][1] wrapper. +#[dart_bridge("flutter/lib/src/native/platform/constraints.g.dart")] +mod constraints { + use dart_sys::Dart_Handle; + + extern "C" { + /// Initializes new empty [MediaStreamConstraints][1]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints + pub fn init() -> Dart_Handle; + + /// Specifies the provided nature and settings of an `audio` + /// [MediaStreamTrack][1] to the given [MediaStreamConstraints][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints + /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + pub fn audio(constraints: Dart_Handle, audio_cons: Dart_Handle); + + /// Specifies the provided nature and settings of a `video` + /// [MediaStreamTrack][1] to the given [MediaStreamConstraints][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints + /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + pub fn video(constraints: Dart_Handle, video_cons: Dart_Handle); + } +} + +/// Dart side representation of [MediaTrackConstraints][0]. /// -/// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints -#[derive(AsRef, Debug)] -pub struct MediaStreamConstraints; +/// [0]: https://w3.org/TR/mediacapture-streams#media-track-constraints +pub struct MediaTrackConstraints(DartMap); + +impl From for Dart_Handle { + fn from(from: MediaTrackConstraints) -> Self { + from.0.into() + } +} + +/// Dart side representation of [MediaStreamConstraints][0]. +/// +/// [0]: https://w3.org/TR/mediacapture-streams#dom-mediastreamconstraints +#[derive(Clone, Debug, From)] +pub struct MediaStreamConstraints(DartHandle); + +impl From for Dart_Handle { + fn from(from: MediaStreamConstraints) -> Self { + from.0.get() + } +} + +impl Default for MediaStreamConstraints { + fn default() -> Self { + Self::new() + } +} impl MediaStreamConstraints { - /// Creates a new [`MediaStreamConstraints`] with none constraints - /// configured. - #[inline] + /// Creates new empty [`MediaStreamConstraints`]. #[must_use] pub fn new() -> Self { - unimplemented!() + unsafe { Self(DartHandle::new(constraints::init())) } } - /// Specifies the nature and settings of the `audio` [MediaStreamTrack][1]. + /// Specifies the provided nature and settings of an `audio` + /// [MediaStreamTrack][1] to these [`MediaStreamConstraints`]. /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack #[inline] pub fn audio(&mut self, audio: AudioTrackConstraints) { - unimplemented!() + unsafe { + constraints::audio( + self.0.get(), + MediaTrackConstraints::from(audio).into(), + ); + } } - /// Specifies the nature and settings of the `video` [MediaStreamTrack][1]. + /// Specifies the provided nature and settings of a `video` + /// [MediaStreamTrack][1] to these [`MediaStreamConstraints`]. /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack #[inline] pub fn video(&mut self, video: DeviceVideoTrackConstraints) { - unimplemented!() + unsafe { + constraints::video( + self.0.get(), + MediaTrackConstraints::from(video).into(), + ); + } } } -impl Default for MediaStreamConstraints { - #[inline] - fn default() -> Self { - Self::new() +/// Dart side representation of [DisplayMediaStreamConstraints][0]. +/// +/// [0]: https://w3.org/TR/screen-capture#dom-displaymediastreamconstraints +#[derive(Clone, Debug, From)] +pub struct DisplayMediaStreamConstraints(DartHandle); + +impl From for Dart_Handle { + fn from(from: DisplayMediaStreamConstraints) -> Self { + from.0.get() } } -/// [DisplayMediaStreamConstraints][1] wrapper. -/// -/// [1]: https://w3.org/TR/screen-capture/#dom-displaymediastreamconstraints -#[derive(AsRef, Debug)] -pub struct DisplayMediaStreamConstraints(); - impl Default for DisplayMediaStreamConstraints { - #[inline] fn default() -> Self { - unimplemented!() + Self::new() } } impl DisplayMediaStreamConstraints { - /// Creates a new [`DisplayMediaStreamConstraints`] with none constraints - /// configured. - #[inline] + /// Creates new empty [`DisplayMediaStreamConstraints`] . #[must_use] pub fn new() -> Self { - unimplemented!() + unsafe { Self(DartHandle::new(constraints::init())) } } - /// Specifies the nature and settings of the `video` [MediaStreamTrack][1]. + /// Specifies the provided nature and settings of a `video` + /// [MediaStreamTrack][1] to these [`DisplayMediaStreamConstraints`]. /// - /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack + /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack #[inline] pub fn video(&mut self, video: DisplayVideoTrackConstraints) { - unimplemented!() + unsafe { + constraints::video( + self.0.get(), + MediaTrackConstraints::from(video).into(), + ); + } + } +} + +impl From for MediaTrackConstraints { + fn from(_: DisplayVideoTrackConstraints) -> Self { + Self(DartMap::new()) + } +} + +impl From for MediaTrackConstraints { + fn from(from: AudioTrackConstraints) -> Self { + let mut audio_cons = DartMap::new(); + let mut ideal_cons = DartMap::new(); + let mut exact_cons = DartMap::new(); + if let Some(device_id) = from.device_id { + match device_id { + ConstrainString::Exact(device_id) => { + exact_cons.set("device_id".to_owned(), device_id.into()); + } + ConstrainString::Ideal(device_id) => { + ideal_cons.set("device_id".to_owned(), device_id.into()); + } + } + } + audio_cons.set("mandatory".to_owned(), exact_cons.as_handle().into()); + audio_cons.set("optional".to_owned(), ideal_cons.as_handle().into()); + + let mut cons = DartMap::new(); + cons.set("audio".to_owned(), audio_cons.as_handle().into()); + + Self(cons) + } +} + +impl From for MediaTrackConstraints { + fn from(from: DeviceVideoTrackConstraints) -> Self { + let mut video_cons = DartMap::new(); + let mut ideal_cons = DartMap::new(); + let mut exact_cons = DartMap::new(); + if let Some(device_id) = from.device_id { + match device_id { + ConstrainString::Exact(device_id) => { + ideal_cons.set("sourceId".to_owned(), device_id.into()); + } + ConstrainString::Ideal(device_id) => { + exact_cons.set("sourceId".to_owned(), device_id.into()); + } + } + } + if let Some(height) = from.height { + match height { + ConstrainU32::Ideal(height) => { + ideal_cons.set("height".to_owned(), height.into()); + } + ConstrainU32::Exact(height) => { + exact_cons.set("height".to_owned(), height.into()); + } + ConstrainU32::Range(min, max) => { + exact_cons.set("minHeight".to_owned(), min.into()); + exact_cons.set("maxHeight".to_owned(), max.into()); + } + } + } + if let Some(width) = from.width { + match width { + ConstrainU32::Ideal(width) => { + ideal_cons.set("width".to_owned(), width.into()); + } + ConstrainU32::Exact(width) => { + exact_cons.set("width".to_owned(), width.into()); + } + ConstrainU32::Range(min, max) => { + exact_cons.set("minWidth".to_owned(), min.into()); + exact_cons.set("maxWidth".to_owned(), max.into()); + } + } + } + if let Some(facing_mode) = from.facing_mode { + match facing_mode { + ConstrainString::Exact(facing_mode) => { + exact_cons.set( + "facing_mode".to_owned(), + facing_mode.to_string().into(), + ); + } + ConstrainString::Ideal(facing_mode) => { + ideal_cons.set( + "facing_mode".to_owned(), + facing_mode.to_string().into(), + ); + } + } + } + video_cons.set("mandatory".to_owned(), exact_cons.as_handle().into()); + video_cons.set("optional".to_owned(), ideal_cons.as_handle().into()); + + let mut cons = DartMap::new(); + cons.set("video".to_owned(), video_cons.as_handle().into()); + + Self(cons) } } diff --git a/src/platform/dart/input_device_info.rs b/src/platform/dart/input_device_info.rs index a0b459bef..c04e6ae79 100644 --- a/src/platform/dart/input_device_info.rs +++ b/src/platform/dart/input_device_info.rs @@ -1,65 +1,119 @@ -//! [MediaDeviceInfo][1] related objects. +//! [MediaDeviceInfo][0] related representations. //! -//! [1]: https://w3.org/TR/mediacapture-streams/#device-info +//! [0]: https://w3.org/TR/mediacapture-streams#device-info -use derive_more::Display; +use std::convert::{TryFrom, TryInto}; -use crate::media::MediaKind; +use medea_macro::dart_bridge; -/// Errors that may occur when parsing [MediaDeviceInfo][1]. -/// -/// [1]: https://w3.org/TR/mediacapture-streams/#device-info -#[derive(Debug, Display)] -pub enum Error { - /// Occurs when kind of media device is not an input device. - #[display(fmt = "Not an input device")] - NotInputDevice, +use crate::{ + api::c_str_into_string, + media::MediaKind, + platform::dart::utils::{handle::DartHandle, NonNullDartValueArgExt}, +}; + +#[dart_bridge("flutter/lib/src/native/platform/input_device_info.g.dart")] +mod input_device_info { + use std::{os::raw::c_char, ptr}; + + use dart_sys::Dart_Handle; + + use crate::api::DartValueArg; + + extern "C" { + /// Returns an unique identifier of the provided device. + pub fn device_id(info: Dart_Handle) -> ptr::NonNull; + + /// Returns a kind of the provided device. + pub fn kind(info: Dart_Handle) -> i64; + + /// Returns a label describing the provided device (for example, + /// "External USB Webcam"). + /// + /// If the provided device has no associated label, then returns an + /// empty string. + pub fn label(info: Dart_Handle) -> ptr::NonNull; + + /// Returns a group identifier of the provided device. + pub fn group_id( + info: Dart_Handle, + ) -> ptr::NonNull>>; + } } -/// Representation of [MediaDeviceInfo][1]. +/// Representation of a [MediaDeviceInfo][0] ONLY for input devices. /// -/// [1]: https://w3.org/TR/mediacapture-streams/#device-info -pub struct InputDeviceInfo; +/// [0]: https://w3.org/TR/mediacapture-streams#device-info +#[derive(Clone, Debug)] +pub struct InputDeviceInfo { + /// Handle to the Dart side `InputDeviceInfo`. + handle: DartHandle, + + /// [`MediaKind`] of this [`InputDeviceInfo`]. + kind: MediaKind, +} impl InputDeviceInfo { - /// Returns unique identifier for the represented device. - #[inline] + /// Returns a unique identifier of the device represented by this + /// [`InputDeviceInfo`]. #[must_use] pub fn device_id(&self) -> String { - unimplemented!() + unsafe { + c_str_into_string(input_device_info::device_id(self.handle.get())) + } } - /// Returns kind of the represented device. - /// - /// This representation of [MediaDeviceInfo][1] ONLY for input device. - /// - /// [1]: https://w3.org/TR/mediacapture-streams/#device-info - #[inline] + /// Returns a kind of the device represented by this [`InputDeviceInfo`]. #[must_use] pub fn kind(&self) -> MediaKind { - unimplemented!() + self.kind } - /// Returns label describing the represented device (for example - /// "External USB Webcam"). + /// Returns a label describing the device represented by this + /// [`InputDeviceInfo`] (for example, "External USB Webcam"). + /// /// If the device has no associated label, then returns an empty string. - #[inline] #[must_use] pub fn label(&self) -> String { - unimplemented!() + unsafe { + c_str_into_string(input_device_info::label(self.handle.get())) + } } - /// Returns group identifier of the represented device. + /// Returns a group identifier of the device represented by this + /// [`InputDeviceInfo`] /// /// Two devices have the same group identifier if they belong to the same /// physical device. For example, the audio input and output devices /// representing the speaker and microphone of the same headset have the /// same [groupId][1]. /// - /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadeviceinfo-groupid - #[inline] + /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediadeviceinfo-groupid #[must_use] - pub fn group_id(&self) -> String { - unimplemented!() + pub fn group_id(&self) -> Option { + Option::try_from(unsafe { + input_device_info::group_id(self.handle.get()).unbox() + }) + .unwrap() + } +} + +impl TryFrom for InputDeviceInfo { + type Error = NotInput; + + fn try_from(value: DartHandle) -> Result { + let kind = unsafe { input_device_info::kind(value.get()) } + .try_into() + .map_err(|_| NotInput)?; + + Ok(Self { + handle: value, + kind, + }) } } + +/// Error of a [MediaDeviceInfo][0] representing not an input device. +/// +/// [0]: https://w3.org/TR/mediacapture-streams#device-info +pub struct NotInput; diff --git a/src/platform/dart/media_devices.rs b/src/platform/dart/media_devices.rs index ca0168c5d..501642446 100644 --- a/src/platform/dart/media_devices.rs +++ b/src/platform/dart/media_devices.rs @@ -1,66 +1,126 @@ -//! [MediaDevices][1] functionality. +//! Representation of [MediaDevices][0]. //! -//! [1]: https://w3.org/TR/mediacapture-streams#mediadevices +//! [0]: https://w3.org/TR/mediacapture-streams#mediadevices +use std::convert::TryInto as _; + +use medea_macro::dart_bridge; use tracerr::Traced; use crate::platform::{ - DisplayMediaStreamConstraints, Error, InputDeviceInfo, - MediaStreamConstraints, MediaStreamTrack, + dart::utils::{ + dart_future::FutureFromDart, handle::DartHandle, list::DartList, + }, + Error, +}; + +use super::{ + constraints::{DisplayMediaStreamConstraints, MediaStreamConstraints}, + input_device_info::InputDeviceInfo, + media_track::MediaStreamTrack, }; -/// Collects information about the User Agent's available media input devices. +#[dart_bridge("flutter/lib/src/native/platform/media_devices.g.dart")] +mod media_devices { + use dart_sys::Dart_Handle; + + extern "C" { + /// Returns information about available media input devices. + pub fn enumerate_devices() -> Dart_Handle; + + /// Prompts a user for permissions to use a media input device, + /// producing a vector of [MediaStreamTrack][1]s containing the + /// requested types of media. + /// + /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + pub fn get_user_media(constraints: Dart_Handle) -> Dart_Handle; + + /// Prompts a user to select and grant permissions to capture contents + /// of a display or portion thereof (such as a single window), producing + /// a vector of [MediaStreamTrack][1]s containing the requested types + /// of media. + /// + /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + pub fn get_display_media(constraints: Dart_Handle) -> Dart_Handle; + } +} + +/// Collects information about available media input devices. /// /// Adapter for a [MediaDevices.enumerateDevices()][1] function. /// /// # Errors /// -/// With [`MediaManagerError::EnumerateDevicesFailed`] if -/// [MediaDevices.enumerateDevices()][1] returns error. +/// If [MediaDevices.enumerateDevices()][1] errors itself or unable to get +/// [MediaDevices][2]. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-enumeratedevices /// [2]: https://w3.org/TR/mediacapture-streams#mediadevices -#[allow(clippy::unused_async)] pub async fn enumerate_devices() -> Result, Traced> { - unimplemented!(); + let devices = FutureFromDart::execute::(unsafe { + media_devices::enumerate_devices() + }) + .await + .map(DartList::from) + .map_err(tracerr::wrap!())?; + + let len = devices.length(); + let mut result = Vec::with_capacity(len); + for i in 0..len { + let val = devices.get(i).unwrap(); + if let Ok(v) = val.try_into() { + result.push(v); + } + } + Ok(result) } -/// Prompts a user for a permission to use a media input which produces vector -/// of [`MediaStreamTrack`]s containing the requested types of media. +/// Prompts a user for permissions to use a media input device, producing a +/// [`Vec`] of [`MediaStreamTrack`]s containing the requested types of media. /// /// Adapter for a [MediaDevices.getUserMedia()][1] function. /// /// # Errors /// -/// With [`MediaManagerError::GetUserMediaFailed`] if -/// [MediaDevices.getUserMedia()][1] returns error. +/// If [MediaDevices.getUserMedia()][1] errors itself or unable to get +/// [MediaDevices][2]. /// /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediadevices-getusermedia /// [2]: https://w3.org/TR/mediacapture-streams#mediadevices -#[allow(clippy::unused_async)] pub async fn get_user_media( caps: MediaStreamConstraints, ) -> Result, Traced> { - unimplemented!(); + let tracks = FutureFromDart::execute::(unsafe { + media_devices::get_user_media(caps.into()) + }) + .await + .map_err(tracerr::wrap!())?; + + Ok(DartList::from(tracks).into()) } -/// Prompts a user to select and grant a permission to capture contents of a -/// display or portion thereof (such as a single window) as vector of -/// [`MediaStreamTrack`]. +/// Prompts a user to select and grant permissions to capture contents of a +/// display or portion thereof (such as a single window), producing a [`Vec`] of +/// [`MediaStreamTrack`]s containing the requested types of media. /// /// Adapter for a [MediaDevices.getDisplayMedia()][1] function. /// /// # Errors /// -/// With [`MediaManagerError::GetUserMediaFailed`] if -/// [MediaDevices.getDisplayMedia()][1] returns error. +/// If [MediaDevices.getDisplayMedia()][1] errors itself or unable to get +/// [MediaDevices][2]. /// -/// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia +/// [1]: https://w3.org/TR/screen-capture#dom-mediadevices-getdisplaymedia /// [2]: https://w3.org/TR/mediacapture-streams#mediadevices -#[allow(clippy::unused_async)] pub async fn get_display_media( caps: DisplayMediaStreamConstraints, ) -> Result, Traced> { - unimplemented!(); + let tracks = FutureFromDart::execute::(unsafe { + media_devices::get_display_media(caps.into()) + }) + .await + .map_err(tracerr::wrap!())?; + + Ok(DartList::from(tracks).into()) } diff --git a/src/platform/dart/media_track.rs b/src/platform/dart/media_track.rs index 3efa00207..18a6eefe5 100644 --- a/src/platform/dart/media_track.rs +++ b/src/platform/dart/media_track.rs @@ -1,8 +1,8 @@ -//! Wrapper around [MediaStreamTrack][1]. +//! Representation of a [MediaStreamTrack][0]. //! -//! [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack +//! [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use dart_sys::Dart_Handle; use derive_more::From; @@ -25,55 +25,88 @@ mod media_stream_track { use crate::api::DartValueArg; extern "C" { - /// Returns ID of the provided [`MediaStreamTrack`]. + /// Returns [ID][1] of the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrack-id pub fn id(track: Dart_Handle) -> ptr::NonNull; - /// Returns device ID of the provided [`MediaStreamTrack`]. + /// Returns [device ID][1] of the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://w3.org/TR/mediacapture-streams#dfn-deviceid pub fn device_id(track: Dart_Handle) -> ptr::NonNull; - /// Returns facing mode of the provided [`MediaStreamTrack`]. + /// Returns [kind][1] of the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-kind + pub fn kind(track: Dart_Handle) -> i64; + + /// Returns [facing mode][1] of the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#def-constraint-facingMode pub fn facing_mode( track: Dart_Handle, ) -> ptr::NonNull>>; - /// Returns height of the provided [`MediaStreamTrack`]. + /// Returns [height][1] of the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediatracksettings-height pub fn height( track: Dart_Handle, ) -> ptr::NonNull>>; - /// Returns width of the provided [`MediaStreamTrack`]. + /// Returns [width][1] of the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediatracksettings-width pub fn width( track: Dart_Handle, ) -> ptr::NonNull>>; - /// Sets `enabled` field of the provided [`MediaStreamTrack`] to the - /// provided [`bool`]. - pub fn set_enabled(track: Dart_Handle, is_enabled: i32); + /// Returns [enabled][1] field of the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-enabled + pub fn enabled(track: Dart_Handle) -> bool; + + /// Sets [enabled][1] field of the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-enabled + pub fn set_enabled(track: Dart_Handle, is_enabled: bool); + + /// Returns [readiness state][1] of the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-readystate + pub fn ready_state(track: Dart_Handle) -> i64; - /// Stops provided [`MediaStreamTrack`]. + /// [Stops][1] the provided [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-stop pub fn stop(track: Dart_Handle); - /// Returns `enabled` field of the provided [`MediaStreamTrack`]. - pub fn enabled(track: Dart_Handle) -> i64; - - /// Returns kind of the provided [`MediaStreamTrack`]. - pub fn kind(track: Dart_Handle) -> i64; - - /// Returns readiness state of the provided [`MediaStreamTrack`]. - pub fn ready_state(track: Dart_Handle) -> i64; - - /// Sets `on_ended` callback of the provided [`MediaStreamTrack`]. + /// Sets [`onended`][1] event handler of the provided + /// [MediaStreamTrack][0]. + /// + /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-onended pub fn on_ended(track: Dart_Handle, cb: Dart_Handle); } } -/// Wrapper around [MediaStreamTrack][1] received from a -/// [getUserMedia()][2]/[getDisplayMedia()][3] request. +/// Representation of a [MediaStreamTrack][0] received from a +/// [getUserMedia()][1] or a [getDisplayMedia()][2] request. /// -/// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack -/// [2]: https://w3.org/TR/mediacapture-streams#dom-mediadevices-getusermedia -/// [3]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia -#[derive(Clone, From, Debug)] +/// [0]: https://w3.org/TR/mediacapture-streams#mediastreamtrack +/// [1]: https://w3.org/TR/mediacapture-streams#dom-mediadevices-getusermedia +/// [2]: https://w3.org/TR/screen-capture#dom-mediadevices-getdisplaymedia +#[derive(Clone, Debug, From)] pub struct MediaStreamTrack(DartHandle); impl MediaStreamTrack { @@ -83,54 +116,39 @@ impl MediaStreamTrack { self.0.get() } - /// Returns [`id`] of the underlying [MediaStreamTrack][2]. + /// Returns [ID][1] of this [`MediaStreamTrack`]. /// - /// [`id`]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrack-id - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrack-id #[inline] #[must_use] pub fn id(&self) -> String { unsafe { c_str_into_string(media_stream_track::id(self.0.get())) } } - /// Returns this [`MediaStreamTrack`]'s kind (audio/video). - #[inline] - #[must_use] - pub fn kind(&self) -> MediaKind { - MediaKind::try_from(unsafe { media_stream_track::kind(self.0.get()) }) - .unwrap() - } - - /// Returns [MediaStreamTrackState][1] of the underlying - /// [MediaStreamTrack][2]. + /// Returns [device ID][1] of this [`MediaStreamTrack`]. /// - /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrackstate - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack - #[allow(clippy::unused_self)] + /// [1]: https://w3.org/TR/mediacapture-streams#dfn-deviceid + #[inline] #[must_use] - pub fn ready_state(&self) -> MediaStreamTrackState { - // TODO: Correct implementation requires `flutter_webrtc`-side fixes. - MediaStreamTrackState::Live + pub fn device_id(&self) -> String { + unsafe { + c_str_into_string(media_stream_track::device_id(self.0.get())) + } } - /// Returns a [`deviceId`][1] of the underlying [MediaStreamTrack][2]. + /// Returns [kind][1] of this [`MediaStreamTrack`]. /// - /// [1]: https://tinyurl.com/w3-streams#dom-mediatracksettings-deviceid - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrack-kind #[inline] #[must_use] - pub fn device_id(&self) -> Option { - unsafe { - c_str_into_string(media_stream_track::device_id(self.0.get())) - } - .try_into() - .unwrap() + pub fn kind(&self) -> MediaKind { + MediaKind::try_from(unsafe { media_stream_track::kind(self.0.get()) }) + .unwrap() } - /// Return a [`facingMode`][1] of the underlying [MediaStreamTrack][2]. + /// Returns [facing mode][1] of this [`MediaStreamTrack`]. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediatracksettings-facingmode - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack #[must_use] pub fn facing_mode(&self) -> Option { Option::::try_from(unsafe { @@ -142,10 +160,9 @@ impl MediaStreamTrack { .unwrap() } - /// Returns a [`height`][1] of the underlying [MediaStreamTrack][2]. + /// Returns [height][1] of this [`MediaStreamTrack`]. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediatracksettings-height - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack #[inline] #[must_use] pub fn height(&self) -> Option { @@ -155,10 +172,9 @@ impl MediaStreamTrack { .unwrap() } - /// Return a [`width`][1] of the underlying [MediaStreamTrack][2]. + /// Returns [width][1] of this [`MediaStreamTrack`]. /// - /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediatracksettings-width - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediatracksettings-width #[inline] #[must_use] pub fn width(&self) -> Option { @@ -168,24 +184,38 @@ impl MediaStreamTrack { .unwrap() } - /// Changes an [`enabled`][1] attribute in the underlying - /// [MediaStreamTrack][2]. + /// Returns [enabled][1] field of this [`MediaStreamTrack`]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrack-enabled + #[inline] + #[must_use] + pub fn enabled(&self) -> bool { + unsafe { media_stream_track::enabled(self.0.get()) } + } + + /// Sets [enabled][1] field of this [`MediaStreamTrack`]. /// /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrack-enabled - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack #[inline] pub fn set_enabled(&self, enabled: bool) { unsafe { - media_stream_track::set_enabled(self.0.get(), enabled as i32); + media_stream_track::set_enabled(self.0.get(), enabled); } } - /// Changes a [`readyState`][1] attribute in the underlying - /// [MediaStreamTrack][2] to [`ended`][3]. + /// Returns [readiness state][1] of this [`MediaStreamTrack`]. /// /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-readystate - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack - /// [3]: https://tinyurl.com/w3-streams#idl-def-MediaStreamTrackState.ended + #[allow(clippy::unused_self)] + #[must_use] + pub fn ready_state(&self) -> MediaStreamTrackState { + // TODO: Correct implementation requires `flutter_webrtc`-side fixes. + MediaStreamTrackState::Live + } + + /// [Stops][1] this [`MediaStreamTrack`]. + /// + /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrack-stop #[inline] pub fn stop(&self) { unsafe { @@ -193,23 +223,12 @@ impl MediaStreamTrack { } } - /// Returns an [`enabled`][1] attribute of the underlying - /// [MediaStreamTrack][2]. - /// - /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrack-enabled - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack - #[inline] - #[must_use] - pub fn enabled(&self) -> bool { - unsafe { media_stream_track::enabled(self.0.get()) == 1 } - } - - /// Detects whether a video track captured from display searching - /// [specific fields][1] in its settings. + /// Detects whether this video [`MediaStreamTrack`] is captured from + /// display, searching for [specific fields][1] in its settings. /// /// Only works in Chrome browser at the moment. /// - /// [1]: https://w3.org/TR/screen-capture/#extensions-to-mediatracksettings + /// [1]: https://w3.org/TR/screen-capture#extensions-to-mediatracksettings #[allow(clippy::unused_self)] #[must_use] pub fn guess_is_from_display(&self) -> bool { @@ -217,11 +236,15 @@ impl MediaStreamTrack { false } - /// Forks this [`MediaStreamTrack`]. + /// Forks this [`MediaStreamTrack`], by creating a new [`MediaStreamTrack`] + /// from this [`MediaStreamTrack`] using a [`clone()`][1] method. + /// + /// __NOTE__: It won't clone [`MediaStreamTrack`]'s event handlers. + /// + /// # Naming /// - /// Creates a new [`MediaStreamTrack`] from this [`MediaStreamTrack`] using - /// a [`clone()`][1] method. It won't clone current [`MediaStreamTrack`]'s - /// callbacks. + /// The name of this method intentionally diverges from [the spec one][1] to + /// not interfere with [`Clone`] trait. /// /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediastreamtrack-clone #[must_use] @@ -230,11 +253,9 @@ impl MediaStreamTrack { self.clone() } - /// Sets handler for the [`ended`][1] event on the underlying - /// [MediaStreamTrack][2]. + /// Sets [`onended`][1] event handler of this [`MediaStreamTrack`]. /// - /// [1]: https://tinyurl.com/w3-streams#event-mediastreamtrack-ended - /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack + /// [1]: https://tinyurl.com/w3-streams#dom-mediastreamtrack-onended pub fn on_ended(&self, f: Option) where F: 'static + FnOnce(), diff --git a/src/platform/dart/mod.rs b/src/platform/dart/mod.rs index 8a6466e2c..e4e4c203b 100644 --- a/src/platform/dart/mod.rs +++ b/src/platform/dart/mod.rs @@ -37,7 +37,11 @@ pub use self::{ }; /// TODO: Implement panic hook. -pub fn set_panic_hook() {} +pub fn set_panic_hook() { + std::panic::set_hook(Box::new(|bt| { + log::error!("Rust code panicked {:?}", bt); + })); +} /// Initialize [`android_logger`] as default application logger with min log /// level set to [`log::Level::Debug`]. diff --git a/src/platform/dart/peer_connection.rs b/src/platform/dart/peer_connection.rs index b76cb59b7..f5dadc8db 100644 --- a/src/platform/dart/peer_connection.rs +++ b/src/platform/dart/peer_connection.rs @@ -125,6 +125,9 @@ mod peer_connection { ty: ptr::NonNull, offer: ptr::NonNull, ) -> Dart_Handle; + + /// Closes the provided [`PeerConnection`]. + pub fn close(peer: Dart_Handle); } } @@ -514,6 +517,14 @@ impl RtcPeerConnection { } } +impl Drop for RtcPeerConnection { + fn drop(&mut self) { + unsafe { + peer_connection::close(self.handle.get()); + } + } +} + /// Representation of a Dart SDP type. #[derive(Display)] pub enum RtcSdpType { diff --git a/src/platform/dart/transceiver.rs b/src/platform/dart/transceiver.rs index 8a95d925b..784d54004 100644 --- a/src/platform/dart/transceiver.rs +++ b/src/platform/dart/transceiver.rs @@ -16,10 +16,7 @@ use crate::{ media::track::local, platform, platform::{ - dart::utils::{ - dart_future::FutureFromDart, handle::DartHandle, - NonNullDartValueArgExt, - }, + dart::utils::{dart_future::FutureFromDart, handle::DartHandle}, TransceiverDirection, }, }; @@ -34,7 +31,7 @@ mod transceiver { extern "C" { /// Returns current direction of the provided [`Transceiver`]. - pub fn get_current_direction(transceiver: Dart_Handle) -> Dart_Handle; + pub fn get_direction(transceiver: Dart_Handle) -> Dart_Handle; /// Returns `Send` [`MediaStreamTrack`] of the provided [`Transceiver`]. pub fn get_send_track( @@ -52,13 +49,11 @@ mod transceiver { pub fn drop_sender(transceiver: Dart_Handle) -> Dart_Handle; /// Returns stopped status of the provided [`Transceiver`]. - pub fn is_stopped( - transceiver: Dart_Handle, - ) -> ptr::NonNull>; + pub fn is_stopped(transceiver: Dart_Handle) -> bool; /// Sets `enabled` field of `Send` [`MediaStreamTrack`] of the provided /// [`Transceiver`]. - pub fn set_send_track_enabled(transceiver: Dart_Handle, enabled: i32); + pub fn set_send_track_enabled(transceiver: Dart_Handle, enabled: bool); /// Returns MID of the provided [`Transceiver`]. pub fn mid( @@ -67,7 +62,7 @@ mod transceiver { /// Returns `1` if the provided [`Transceiver`] has `Send` /// [`MediaStreamTrack`]. - pub fn has_send_track(transceiver: Dart_Handle) -> i8; + pub fn has_send_track(transceiver: Dart_Handle) -> bool; /// Sets `direction` of this [`Transceiver`]. pub fn set_direction( @@ -95,10 +90,8 @@ impl Transceiver { ) -> LocalBoxFuture<'static, ()> { let this = self.clone(); Box::pin(async move { - this.set_direction( - this.current_direction().await - disabled_direction, - ) - .await; + this.set_direction(this.direction().await - disabled_direction) + .await; }) } @@ -109,17 +102,15 @@ impl Transceiver { ) -> LocalBoxFuture<'static, ()> { let this = self.clone(); Box::pin(async move { - this.set_direction( - this.current_direction().await | enabled_direction, - ) - .await; + this.set_direction(this.direction().await | enabled_direction) + .await; }) } /// Indicates whether the provided [`TransceiverDirection`] is enabled for /// this [`Transceiver`]. pub async fn has_direction(&self, direction: TransceiverDirection) -> bool { - self.current_direction().await.contains(direction) + self.direction().await.contains(direction) } /// Replaces [`TransceiverDirection::SEND`] [`local::Track`] of this @@ -180,7 +171,7 @@ impl Transceiver { /// Indicates whether this [`Transceiver`] has [`local::Track`]. #[must_use] pub fn has_send_track(&self) -> bool { - unsafe { transceiver::has_send_track(self.transceiver.get()) == 1 } + unsafe { transceiver::has_send_track(self.transceiver.get()) } } /// Sets the underlying [`local::Track`]'s `enabled` field to the provided @@ -194,10 +185,7 @@ impl Transceiver { )) .unwrap() { - transceiver::set_send_track_enabled( - sender.get(), - enabled as i32, - ); + transceiver::set_send_track_enabled(sender.get(), enabled); } } } @@ -205,17 +193,15 @@ impl Transceiver { /// Indicates whether the underlying [RTCRtpTransceiver] is stopped. #[must_use] pub fn is_stopped(&self) -> bool { - let val = - unsafe { transceiver::is_stopped(self.transceiver.get()).unbox() }; - i8::try_from(val).unwrap() == 1 + unsafe { transceiver::is_stopped(self.transceiver.get()) } } /// Returns current [`TransceiverDirection`] of this [`Transceiver`]. - fn current_direction(&self) -> impl Future { + fn direction(&self) -> impl Future { let handle = self.transceiver.get(); async move { FutureFromDart::execute::(unsafe { - transceiver::get_current_direction(handle) + transceiver::get_direction(handle) }) .await .unwrap() diff --git a/src/platform/dart/transport.rs b/src/platform/dart/transport.rs index 92c031092..ffed8ba0f 100644 --- a/src/platform/dart/transport.rs +++ b/src/platform/dart/transport.rs @@ -1,68 +1,198 @@ -//! [WebSocket] transport wrapper. -//! -//! [WebSocket]: https://developer.mozilla.org/docs/WebSockets +use std::{ + cell::{Cell, RefCell}, + rc::Rc, +}; -use futures::stream::LocalBoxStream; -use medea_client_api_proto::{ClientMsg, ServerMsg}; +use futures::{ + channel::{mpsc, mpsc::UnboundedSender}, + prelude::stream::LocalBoxStream, +}; +use medea_client_api_proto::{ClientMsg, CloseReason, ServerMsg}; +use medea_macro::dart_bridge; +use medea_reactive::ObservableCell; use tracerr::Traced; use crate::{ - platform::transport::{RpcTransport, TransportError, TransportState}, - rpc::{websocket::ClientDisconnect, ApiUrl}, + api::string_into_c_str, + platform::{ + dart::utils::{ + callback::Callback, dart_future::FutureFromDart, handle::DartHandle, + }, + RpcTransport, TransportError, TransportState, + }, + rpc::{ApiUrl, ClientDisconnect, CloseMsg}, }; type Result> = std::result::Result; -/// [WebSocket] [`RpcTransport`] between a client and a server. -/// -/// # Drop -/// -/// This structure has __cyclic references__, which are freed in its [`Drop`] -/// implementation. -/// -/// If you're adding new cyclic dependencies, then don't forget to drop them in -/// the [`Drop`]. +#[dart_bridge("flutter/lib/src/native/platform/transport.g.dart")] +mod transport { + use std::{os::raw::c_char, ptr}; + + use dart_sys::Dart_Handle; + + extern "C" { + /// [Connects][1] to the provided `url` and returns the created + /// [`WebSocket`][0]. + /// + /// [Subscribes][2] to the created [`WebSocket`][0] passing the given + /// `on_message` and `on_close` callbacks. + /// + /// [0]: https://api.dart.dev/stable/dart-io/WebSocket-class.html + /// [1]: https://api.dart.dev/stable/dart-io/WebSocket/connect.html + /// [2]: https://api.dart.dev/stable/dart-async/Stream/listen.html + pub fn connect( + url: ptr::NonNull, + on_message: Dart_Handle, + on_close: Dart_Handle, + ) -> Dart_Handle; + + /// [Sends][1] the provided `message` via the provided [`WebSocket`][0]. + /// + /// [0]: https://api.dart.dev/stable/dart-io/WebSocket-class.html + /// [1]: https://api.dart.dev/stable/dart-io/WebSocket/add.html + pub fn send(transport: Dart_Handle, message: ptr::NonNull); + + /// [Closes][1] the provided [`WebSocket`][0] connection. + /// + /// [0]: https://api.dart.dev/stable/dart-io/WebSocket-class.html + /// [1]: https://api.dart.dev/stable/dart-io/WebSocket/close.html + pub fn close( + transport: Dart_Handle, + close_code: i32, + close_msg: ptr::NonNull, + ); + } +} + +/// [`RpcTransport`] implementation of a Dart side [`WebSocket`][0]. /// -/// [WebSocket]: https://developer.mozilla.org/docs/WebSockets -pub struct WebSocketRpcTransport; +/// [0]: https://api.dart.dev/stable/dart-io/WebSocket-class.html +#[derive(Clone, Debug)] +pub struct WebSocketRpcTransport { + /// Handle to the Dart side [`WebSocket`][0]. + /// + /// [0]: https://api.dart.dev/stable/dart-io/WebSocket-class.html + handle: DartHandle, + + /// Subscribers to the messages received by this transport. + on_message_subs: Rc>>>, + + /// Reason of [`WebSocketRpcTransport`] closing. + /// + /// Is sent in a [WebSocket close frame][1]. + /// + /// [1]: https://tools.ietf.org/html/rfc6455#section-5.5.1 + close_reason: Cell, + + /// State of this [`WebSocketRpcTransport`] connection. + socket_state: Rc>, +} impl WebSocketRpcTransport { - /// Initiates a new [WebSocket] connection. Resolves only once an underlying - /// connection becomes active. + /// Initiates a new [`WebSocketRpcTransport`] connection. + /// + /// Only resolves once the underlying connection becomes active. /// /// # Errors /// - /// With [`TransportError::CreateSocket`] if cannot establish [WebSocket] to - /// specified URL. + /// With [`TransportError::CreateSocket`] if cannot establish + /// [`WebSocket`][0] to the specified `url`. /// /// With [`TransportError::InitSocket`] if [WebSocket.onclose][1] callback /// fired before [WebSocket.onopen][2] callback. /// - /// [WebSocket]: https://developer.mozilla.org/docs/WebSockets + /// [0]: https://api.dart.dev/stable/dart-io/WebSocket-class.html /// [1]: https://developer.mozilla.org/docs/Web/API/WebSocket/onclose /// [2]: https://developer.mozilla.org/docs/Web/API/WebSocket/onopen pub async fn new(url: ApiUrl) -> Result { - unimplemented!() + unsafe { + let on_message_subs = Rc::new(RefCell::new(Vec::new())); + let socket_state = + Rc::new(ObservableCell::new(TransportState::Open)); + let handle = + FutureFromDart::execute::(transport::connect( + string_into_c_str(url.as_ref().to_string()), + Callback::from_fn_mut({ + let subs = Rc::clone(&on_message_subs); + move |msg: String| { + let msg = + match serde_json::from_str::(&msg) { + Ok(parsed) => parsed, + Err(e) => { + // TODO: Protocol versions mismatch? + // should drop connection if so. + log::error!("{}", tracerr::new!(e)); + return; + } + }; + + subs.borrow_mut().retain( + |sub: &UnboundedSender| { + sub.unbounded_send(msg.clone()).is_ok() + }, + ); + } + }) + .into_dart(), + Callback::from_fn_mut({ + let socket_state = Rc::clone(&socket_state); + move |msg: ()| { + socket_state.set(TransportState::Closed( + CloseMsg::Normal(1000, CloseReason::Finished), + )); + } + }) + .into_dart(), + )) + .await + .map_err(|_| tracerr::new!(TransportError::InitSocket))?; + + Ok(Self { + handle, + on_message_subs, + socket_state, + close_reason: Cell::new( + ClientDisconnect::RpcTransportUnexpectedlyDropped, + ), + }) + } } } impl RpcTransport for WebSocketRpcTransport { #[inline] fn on_message(&self) -> LocalBoxStream<'static, ServerMsg> { - unimplemented!() + let (tx, rx) = mpsc::unbounded(); + self.on_message_subs.borrow_mut().push(tx); + Box::pin(rx) } #[inline] - fn set_close_reason(&self, close_reason: ClientDisconnect) { - unimplemented!() + fn set_close_reason(&self, reason: ClientDisconnect) { + self.close_reason.set(reason); } - fn send(&self, msg: &ClientMsg) -> Result<()> { - unimplemented!() + fn send(&self, msg: &ClientMsg) -> Result<(), Traced> { + let msg = serde_json::to_string(msg).unwrap(); + unsafe { + transport::send(self.handle.get(), string_into_c_str(msg)); + } + Ok(()) } #[inline] fn on_state_change(&self) -> LocalBoxStream<'static, TransportState> { - unimplemented!() + self.socket_state.subscribe() + } +} + +impl Drop for WebSocketRpcTransport { + fn drop(&mut self) { + let rsn = serde_json::to_string(&self.close_reason.get()) + .expect("Could not serialize close message"); + unsafe { + transport::close(self.handle.get(), 1000, string_into_c_str(rsn)); + } } } diff --git a/src/platform/dart/utils/list.rs b/src/platform/dart/utils/list.rs new file mode 100644 index 000000000..92670487a --- /dev/null +++ b/src/platform/dart/utils/list.rs @@ -0,0 +1,75 @@ +//! Rust side representation of a Dart side [`List`]. +//! +//! [`List`]: https://api.dart.dev/stable/dart-core/List-class.html + +use std::convert::TryInto; + +use derive_more::From; +use medea_macro::dart_bridge; + +use crate::platform::dart::utils::{ + handle::DartHandle, NonNullDartValueArgExt, +}; + +#[dart_bridge("flutter/lib/src/native/ffi/list.g.dart")] +mod list { + use std::ptr; + + use dart_sys::Dart_Handle; + + use crate::{api::DartValueArg, platform::dart::utils::handle::DartHandle}; + + extern "C" { + /// Returns an element by the provided `index` from the provided + /// [`List`]. + /// + /// [`List`]: https://api.dart.dev/stable/dart-core/List-class.html + pub fn get( + list: Dart_Handle, + index: u32, + ) -> ptr::NonNull>>; + + /// Returns [`length`] of the provided [`List`]. + /// + /// [`length`]: https://api.dart.dev/stable/dart-core/List/length.html + /// [`List`]: https://api.dart.dev/stable/dart-core/List-class.html + pub fn length(list: Dart_Handle) -> u32; + } +} + +/// Rust side representation of a Dart side [`List`]. +/// +/// [`List`]: https://api.dart.dev/stable/dart-core/List-class.html +#[derive(From)] +pub struct DartList(DartHandle); + +impl DartList { + /// Returns an element by the provided `index` from this [`DartList`]. + #[must_use] + pub fn get(&self, index: usize) -> Option { + #[allow(clippy::cast_possible_truncation)] + unsafe { list::get(self.0.get(), index as u32).unbox() } + .try_into() + .unwrap() + } + + /// Returns [`length`] of this [`DartList`]. + /// + /// [`length`]: https://api.dart.dev/stable/dart-core/List/length.html + #[must_use] + pub fn length(&self) -> usize { + unsafe { list::length(self.0.get()) as usize } + } +} + +impl> From for Vec { + fn from(list: DartList) -> Self { + let len = list.length(); + let mut out = Vec::with_capacity(len); + for i in 0..len { + let val = list.get(i).unwrap(); + out.push(val.into()); + } + out + } +} diff --git a/src/platform/dart/utils/map.rs b/src/platform/dart/utils/map.rs new file mode 100644 index 000000000..e702e8219 --- /dev/null +++ b/src/platform/dart/utils/map.rs @@ -0,0 +1,75 @@ +//! Rust side representation of a Dart side [`Map`]. +//! +//! [`Map`]: https://api.dart.dev/stable/dart-core/Map-class.html + +use dart_sys::Dart_Handle; +use medea_macro::dart_bridge; + +use crate::{ + api::{string_into_c_str, DartValue}, + platform::dart::utils::handle::DartHandle, +}; + +#[dart_bridge("flutter/lib/src/native/ffi/map.g.dart")] +mod map { + use std::{os::raw::c_char, ptr}; + + use dart_sys::Dart_Handle; + + use crate::api::DartValue; + + extern "C" { + /// Initializes a new empty [`Map`]. + /// + /// [`Map`]: https://api.dart.dev/stable/dart-core/Map-class.html + pub fn init() -> Dart_Handle; + + /// Sets the provided `value` under the provided `key` to the provided + /// [`Map`]. + /// + /// [`Map`]: https://api.dart.dev/stable/dart-core/Map-class.html + pub fn set( + map: Dart_Handle, + key: ptr::NonNull, + value: DartValue, + ); + } +} + +/// Rust representation of a Dart side [`Map`]. +/// +/// [`Map`]: https://api.dart.dev/stable/dart-core/Map-class.html +pub struct DartMap(DartHandle); + +impl From for Dart_Handle { + fn from(from: DartMap) -> Self { + from.0.get() + } +} + +impl Default for DartMap { + fn default() -> Self { + Self::new() + } +} + +impl DartMap { + /// Creates a new empty [`DartMap`]. + #[must_use] + pub fn new() -> Self { + Self(DartHandle::new(unsafe { map::init() })) + } + + /// Sets the provided `value` under the provided `key` to this [`DartMap`]. + pub fn set(&mut self, key: String, value: DartValue) { + unsafe { + map::set(self.0.get(), string_into_c_str(key), value); + } + } + + /// Returns the underlying [`Dart_Handle`] of this [`DartMap`]. + #[must_use] + pub fn as_handle(&self) -> Dart_Handle { + self.0.get() + } +} diff --git a/src/platform/dart/utils/mod.rs b/src/platform/dart/utils/mod.rs index 0f7ae2b36..43a7db19e 100644 --- a/src/platform/dart/utils/mod.rs +++ b/src/platform/dart/utils/mod.rs @@ -6,6 +6,8 @@ pub mod dart_api; pub mod dart_future; pub mod function; pub mod handle; +pub mod list; +pub mod map; use std::ptr; diff --git a/src/platform/wasm/input_device_info.rs b/src/platform/wasm/input_device_info.rs index 54508364c..ea657ab24 100644 --- a/src/platform/wasm/input_device_info.rs +++ b/src/platform/wasm/input_device_info.rs @@ -82,8 +82,8 @@ impl InputDeviceInfo { /// [1]: https://w3.org/TR/mediacapture-streams/#dom-mediadeviceinfo-groupid #[inline] #[must_use] - pub fn group_id(&self) -> String { - self.info.group_id() + pub fn group_id(&self) -> Option { + Some(self.info.group_id()) } } diff --git a/src/platform/wasm/media_track.rs b/src/platform/wasm/media_track.rs index e6d28b026..4439ea33e 100644 --- a/src/platform/wasm/media_track.rs +++ b/src/platform/wasm/media_track.rs @@ -96,14 +96,19 @@ impl MediaStreamTrack { /// Returns a [`deviceId`][1] of the underlying [MediaStreamTrack][2]. /// + /// # Panics + /// + /// If the underlying [MediaStreamTrack][2] doesn't have [`deviceId`][1]. + /// /// [1]: https://tinyurl.com/w3-streams#dom-mediatracksettings-deviceid /// [2]: https://w3.org/TR/mediacapture-streams#mediastreamtrack #[inline] #[must_use] - pub fn device_id(&self) -> Option { + pub fn device_id(&self) -> String { get_property_by_name(&self.sys_track.get_settings(), "deviceId", |v| { v.as_string() }) + .unwrap() } /// Return a [`facingMode`][1] of the underlying [MediaStreamTrack][2]. diff --git a/src/platform/wasm/transceiver.rs b/src/platform/wasm/transceiver.rs index e1b15a71c..fd4b5d493 100644 --- a/src/platform/wasm/transceiver.rs +++ b/src/platform/wasm/transceiver.rs @@ -23,7 +23,7 @@ impl Transceiver { /// Returns current [`TransceiverDirection`] of this [`Transceiver`]. #[inline] #[must_use] - fn current_direction(&self) -> TransceiverDirection { + fn direction(&self) -> TransceiverDirection { TransceiverDirection::from(self.transceiver.direction()) } @@ -60,7 +60,7 @@ impl Transceiver { /// Indicates whether the provided [`TransceiverDirection`] is enabled for /// this [`Transceiver`]. pub async fn has_direction(&self, direction: TransceiverDirection) -> bool { - self.current_direction().contains(direction) + self.direction().contains(direction) } /// Replaces [`TransceiverDirection::SEND`] [`local::Track`] of this