diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b8813ef8..8ad120db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,8 @@ All user visible changes to this project will be documented in this file. This p - `FormatException`; - `MediaStateTransitionException`; - `MediaSettingsUpdateException`. - - `MediaManagerHandle.set_output_audio_id()` function switching output audio device on Dart platform ([#29]). + - `MediaManagerHandle.set_output_audio_id()` function switching output audio device on Dart platform ([#29]); + - `MediaManagerHandle.on_device_change()` callback firing whenever `MediaManagerHandle.enumerate_devices()` list changes ([#30]). ### Updated @@ -68,6 +69,7 @@ All user visible changes to this project will be documented in this file. This p [#4]: /../../pull/4 [#16]: /../../pull/16 [#29]: /../../pull/29 +[#30]: /../../pull/30 diff --git a/Makefile b/Makefile index acd90ed8e..8c4cc5977 100644 --- a/Makefile +++ b/Makefile @@ -952,8 +952,7 @@ ifeq ($(browser),firefox) ghcr.io/instrumentisto/geckodriver:$(FIREFOX_VERSION) \ --binary=/opt/firefox/firefox else - DRIVER_ARGS="--disable-dev-shm-usage" \ - docker run --rm -d --network=host \ + docker run --rm -d --network=host --shm-size 512m \ --name medea-webdriver-chrome \ selenoid/chrome:$(CHROME_VERSION) endif diff --git a/flutter/android/src/main/jniLibs/arm64-v8a/libmedea_jason.so b/flutter/android/src/main/jniLibs/arm64-v8a/libmedea_jason.so index d44901f01..81e3cca32 100755 Binary files a/flutter/android/src/main/jniLibs/arm64-v8a/libmedea_jason.so and b/flutter/android/src/main/jniLibs/arm64-v8a/libmedea_jason.so differ diff --git a/flutter/android/src/main/jniLibs/armeabi-v7a/libmedea_jason.so b/flutter/android/src/main/jniLibs/armeabi-v7a/libmedea_jason.so index cdd86078e..7635bade1 100755 Binary files a/flutter/android/src/main/jniLibs/armeabi-v7a/libmedea_jason.so and b/flutter/android/src/main/jniLibs/armeabi-v7a/libmedea_jason.so differ diff --git a/flutter/android/src/main/jniLibs/x86/libmedea_jason.so b/flutter/android/src/main/jniLibs/x86/libmedea_jason.so index 614e586bb..17a4cb8be 100755 Binary files a/flutter/android/src/main/jniLibs/x86/libmedea_jason.so and b/flutter/android/src/main/jniLibs/x86/libmedea_jason.so differ diff --git a/flutter/android/src/main/jniLibs/x86_64/libmedea_jason.so b/flutter/android/src/main/jniLibs/x86_64/libmedea_jason.so index 5756fd045..c77e2e8ea 100755 Binary files a/flutter/android/src/main/jniLibs/x86_64/libmedea_jason.so and b/flutter/android/src/main/jniLibs/x86_64/libmedea_jason.so differ diff --git a/flutter/assets/pkg/medea_jason.js b/flutter/assets/pkg/medea_jason.js index 528ae292c..6b6604c32 100644 --- a/flutter/assets/pkg/medea_jason.js +++ b/flutter/assets/pkg/medea_jason.js @@ -21,6 +21,15 @@ function takeObject(idx) { return ret; } +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + let WASM_VECTOR_LEN = 0; let cachegetUint8Memory0 = null; @@ -84,6 +93,10 @@ function passStringToWasm0(arg, malloc, realloc) { return ptr; } +function isLikeNone(x) { + return x === undefined || x === null; +} + let cachegetInt32Memory0 = null; function getInt32Memory0() { if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) { @@ -92,19 +105,6 @@ function getInt32Memory0() { return cachegetInt32Memory0; } -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; -} - -function isLikeNone(x) { - return x === undefined || x === null; -} - let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); cachedTextDecoder.decode(); @@ -252,57 +252,10 @@ function handleError(f, args) { function getArrayU8FromWasm0(ptr, len) { return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); } -function __wbg_adapter_343(arg0, arg1, arg2, arg3) { +function __wbg_adapter_344(arg0, arg1, arg2, arg3) { wasm.wasm_bindgen__convert__closures__invoke2_mut__h506acbb810aa0090(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); } -/** -* Possible error kinds of a [`LocalMediaInitException`]. -*/ -export const LocalMediaInitExceptionKind = Object.freeze({ -/** -* Occurs if the [getUserMedia()][1] request failed. -* -* [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia -*/ -GetUserMediaFailed:0,"0":"GetUserMediaFailed", -/** -* Occurs if the [getDisplayMedia()][1] request failed. -* -* [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia -*/ -GetDisplayMediaFailed:1,"1":"GetDisplayMediaFailed", -/** -* Occurs when local track is [`ended`][1] right after [getUserMedia()][2] -* or [getDisplayMedia()][3] request. -* -* [1]: https://tinyurl.com/w3-streams#idl-def-MediaStreamTrackState.ended -* [2]: https://tinyurl.com/rnxcavf -* [3]: https://w3.org/TR/screen-capture#dom-mediadevices-getdisplaymedia -*/ -LocalTrackIsEnded:2,"2":"LocalTrackIsEnded", }); -/** -* Possible error kinds of a [`RpcClientException`]. -*/ -export const RpcClientExceptionKind = Object.freeze({ -/** -* Connection with a server was lost. -* -* This usually means that some transport error occurred, so a client can -* continue performing reconnecting attempts. -*/ -ConnectionLost:0,"0":"ConnectionLost", -/** -* Could not authorize an RPC session. -* -* This usually means that authentication data a client provides is -* obsolete. -*/ -AuthorizationFailed:1,"1":"AuthorizationFailed", -/** -* RPC session has been finished. This is a terminal state. -*/ -SessionFinished:2,"2":"SessionFinished", }); /** * Describes directions that a camera can face, as seen from a user's * perspective. Representation of a [VideoFacingModeEnum][1]. @@ -341,19 +294,19 @@ Display:1,"1":"Display", }); /** * [MediaDeviceInfo.kind][1] representation. * -* [1]: https://www.w3.org/TR/mediacapture-streams/#dom-mediadeviceinfo-kind +* [1]: https://w3.org/TR/mediacapture-streams#dom-mediadeviceinfo-kind */ export const MediaDeviceKind = Object.freeze({ /** -* Represents an audio input device; for example a microphone. +* Audio input device (for example, a microphone). */ AudioInput:0,"0":"AudioInput", /** -* Represents a video input device; for example a webcam. +* Video input device (for example, a webcam). */ VideoInput:1,"1":"VideoInput", /** -* Represents an audio output device; for example a pair of headphones. +* Audio output device (for example, a pair of headphones). */ AudioOutput:2,"2":"AudioOutput", }); /** @@ -371,6 +324,53 @@ Audio:0,"0":"Audio", */ Video:1,"1":"Video", }); /** +* Possible error kinds of a [`LocalMediaInitException`]. +*/ +export const LocalMediaInitExceptionKind = Object.freeze({ +/** +* Occurs if the [getUserMedia()][1] request failed. +* +* [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia +*/ +GetUserMediaFailed:0,"0":"GetUserMediaFailed", +/** +* Occurs if the [getDisplayMedia()][1] request failed. +* +* [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia +*/ +GetDisplayMediaFailed:1,"1":"GetDisplayMediaFailed", +/** +* Occurs when local track is [`ended`][1] right after [getUserMedia()][2] +* or [getDisplayMedia()][3] request. +* +* [1]: https://tinyurl.com/w3-streams#idl-def-MediaStreamTrackState.ended +* [2]: https://tinyurl.com/rnxcavf +* [3]: https://w3.org/TR/screen-capture#dom-mediadevices-getdisplaymedia +*/ +LocalTrackIsEnded:2,"2":"LocalTrackIsEnded", }); +/** +* Possible error kinds of a [`RpcClientException`]. +*/ +export const RpcClientExceptionKind = Object.freeze({ +/** +* Connection with a server was lost. +* +* This usually means that some transport error occurred, so a client can +* continue performing reconnecting attempts. +*/ +ConnectionLost:0,"0":"ConnectionLost", +/** +* Could not authorize an RPC session. +* +* This usually means that authentication data a client provides is +* obsolete. +*/ +AuthorizationFailed:1,"1":"AuthorizationFailed", +/** +* RPC session has been finished. This is a terminal state. +*/ +SessionFinished:2,"2":"SessionFinished", }); +/** * Constraints applicable to audio tracks. */ export class AudioTrackConstraints { @@ -1292,6 +1292,23 @@ export class MediaManagerHandle { var ret = wasm.mediamanagerhandle_init_local_tracks(this.ptr, caps.ptr); return takeObject(ret); } + /** + * Subscribes on the [`MediaManagerHandle`]'s `devicechange` event. + * @param {Function} cb + */ + on_device_change(cb) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.mediamanagerhandle_on_device_change(retptr, this.ptr, addHeapObject(cb)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } } /** * Errors occurring in [`RoomHandle::set_local_media_settings()`][1] method. @@ -2423,20 +2440,37 @@ async function init(input) { var ret = LocalMediaTrack.__wrap(arg0); return addHeapObject(ret); }; - imports.wbg.__wbindgen_is_string = function(arg0) { - var ret = typeof(getObject(arg0)) === 'string'; - return ret; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + var ret = getObject(arg0); + return addHeapObject(ret); }; - imports.wbg.__wbindgen_json_serialize = function(arg0, arg1) { + imports.wbg.__wbindgen_number_new = function(arg0) { + var ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { const obj = getObject(arg1); - var ret = JSON.stringify(obj === undefined ? null : obj); - var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + 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.__wbindgen_number_new = function(arg0) { - var ret = arg0; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = takeObject(arg0).original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + var ret = false; + return ret; + }; + imports.wbg.__wbg_roomclosereason_new = function(arg0) { + var ret = RoomCloseReason.__wrap(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_mediadeviceinfo_new = function(arg0) { + var ret = MediaDeviceInfo.__wrap(arg0); return addHeapObject(ret); }; imports.wbg.__wbg_stateerror_new = function(arg0) { @@ -2471,26 +2505,25 @@ async function init(input) { var ret = MediaSettingsUpdateException.__wrap(arg0); return addHeapObject(ret); }; - imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + var ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_json_serialize = 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 ret = JSON.stringify(obj === undefined ? null : obj); + var ptr0 = 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.__wbindgen_cb_drop = function(arg0) { - const obj = takeObject(arg0).original; - if (obj.cnt-- == 1) { - obj.a = 0; - return true; - } - var ret = false; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + var ret = getObject(arg0) === undefined; return ret; }; - imports.wbg.__wbg_roomclosereason_new = function(arg0) { - var ret = RoomCloseReason.__wrap(arg0); - return addHeapObject(ret); + imports.wbg.__wbindgen_is_string = function(arg0) { + var ret = typeof(getObject(arg0)) === 'string'; + return ret; }; imports.wbg.__wbg_remotemediatrack_new = function(arg0) { var ret = RemoteMediaTrack.__wrap(arg0); @@ -2500,26 +2533,10 @@ async function init(input) { var ret = ReconnectHandle.__wrap(arg0); return addHeapObject(ret); }; - imports.wbg.__wbg_mediadeviceinfo_new = function(arg0) { - var ret = MediaDeviceInfo.__wrap(arg0); - return addHeapObject(ret); - }; imports.wbg.__wbg_connectionhandle_new = function(arg0) { var ret = ConnectionHandle.__wrap(arg0); return addHeapObject(ret); }; - imports.wbg.__wbindgen_object_clone_ref = function(arg0) { - var ret = getObject(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_string_new = function(arg0, arg1) { - var ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_is_undefined = function(arg0) { - var ret = getObject(arg0) === undefined; - return ret; - }; imports.wbg.__wbindgen_number_get = function(arg0, arg1) { const obj = getObject(arg1); var ret = typeof(obj) === 'number' ? obj : undefined; @@ -2918,7 +2935,7 @@ async function init(input) { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_343(a, state0.b, arg0, arg1); + return __wbg_adapter_344(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -2998,28 +3015,28 @@ async function init(input) { var ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2640 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 710, __wbg_adapter_32); + imports.wbg.__wbindgen_closure_wrapper2657 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 713, __wbg_adapter_32); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2641 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 710, __wbg_adapter_35); + imports.wbg.__wbindgen_closure_wrapper2658 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 713, __wbg_adapter_35); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2642 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 710, __wbg_adapter_38); + imports.wbg.__wbindgen_closure_wrapper2659 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 713, __wbg_adapter_38); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2643 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 710, __wbg_adapter_41); + imports.wbg.__wbindgen_closure_wrapper2660 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 713, __wbg_adapter_41); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2651 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 710, __wbg_adapter_44); + imports.wbg.__wbindgen_closure_wrapper2668 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 713, __wbg_adapter_44); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2748 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 777, __wbg_adapter_47); + imports.wbg.__wbindgen_closure_wrapper2763 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 781, __wbg_adapter_47); return addHeapObject(ret); }; diff --git a/flutter/assets/pkg/medea_jason_bg.wasm b/flutter/assets/pkg/medea_jason_bg.wasm index 7c23fc24b..4316e90b8 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/pubspec.lock b/flutter/example/pubspec.lock index 7f5da1d83..60d86a5cd 100644 --- a/flutter/example/pubspec.lock +++ b/flutter/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.1.6" + version: "3.1.11" async: dependency: transitive description: @@ -49,7 +49,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" crypto: dependency: transitive description: @@ -105,7 +105,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "b7045e30c69b1e95238f51e04743d4054184ecac" + resolved-ref: "110841680107c1c94e1e9b508ed7dfff47562d73" url: "https://github.com/instrumentisto/flutter-webrtc.git" source: git version: "0.7.0+hotfix.2" @@ -139,7 +139,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" medea_jason: dependency: "direct main" description: @@ -160,7 +160,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_provider: dependency: transitive description: @@ -249,7 +249,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -291,7 +291,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.9" typed_data: dependency: transitive description: @@ -305,14 +305,14 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" vm_service: dependency: transitive description: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "7.5.0" + version: "8.2.1" webdriver: dependency: transitive description: diff --git a/flutter/lib/src/interface/media_manager.dart b/flutter/lib/src/interface/media_manager.dart index d8c817c24..b1e72bc68 100644 --- a/flutter/lib/src/interface/media_manager.dart +++ b/flutter/lib/src/interface/media_manager.dart @@ -37,6 +37,9 @@ abstract class MediaManagerHandle { /// Switches output audio device to the device with the provided [deviceId]. Future setOutputAudioId(String deviceId); + /// Subscribes onto the [MediaManagerHandle]'s `devicechange` event. + void onDeviceChange(Function cb); + /// Drops the associated Rust struct and nulls the local [Pointer] to it. @moveSemantics void free(); diff --git a/flutter/lib/src/native/media_manager.dart b/flutter/lib/src/native/media_manager.dart index cd41d0ae6..529d6736d 100644 --- a/flutter/lib/src/native/media_manager.dart +++ b/flutter/lib/src/native/media_manager.dart @@ -23,6 +23,9 @@ typedef _enumerateDevices_Dart = Object Function(Pointer); typedef _setOutputAudioId_C = Handle Function(Pointer, Pointer); typedef _setOutputAudioId_Dart = Object Function(Pointer, Pointer); +typedef _onDeviceChange_C = Void Function(Pointer, Handle); +typedef _onDeviceChange_Dart = void Function(Pointer, Object); + typedef _free_C = Void Function(Pointer); typedef _free_Dart = void Function(Pointer); @@ -38,6 +41,10 @@ final _setOutputAudioId = dl.lookupFunction<_setOutputAudioId_C, _setOutputAudioId_Dart>( 'MediaManagerHandle__set_output_audio_id'); +final _onDeviceChange = + dl.lookupFunction<_onDeviceChange_C, _onDeviceChange_Dart>( + 'MediaManagerHandle__on_device_change'); + final _free = dl.lookupFunction<_free_C, _free_Dart>('MediaManagerHandle__free'); @@ -78,6 +85,11 @@ class NativeMediaManagerHandle extends MediaManagerHandle { as Future); } + @override + void onDeviceChange(Function cb) { + _onDeviceChange(ptr.getInnerPtr(), cb); + } + @moveSemantics @override void free() { diff --git a/flutter/lib/src/native/platform/media_devices.dart b/flutter/lib/src/native/platform/media_devices.dart index c84c7a4cd..0b77bc1f9 100644 --- a/flutter/lib/src/native/platform/media_devices.dart +++ b/flutter/lib/src/native/platform/media_devices.dart @@ -15,6 +15,7 @@ void registerFunctions(DynamicLibrary dl) { getUserMedia: Pointer.fromFunction(_getUserMedia), getDisplayMedia: Pointer.fromFunction(_getDisplayMedia), setOutputAudioId: Pointer.fromFunction(_setOutputAudioId), + onDeviceChange: Pointer.fromFunction(_onDeviceChange), ); } @@ -38,3 +39,8 @@ Object _getDisplayMedia(DisplayConstraints constraints) { Object _setOutputAudioId(Pointer deviceId) { return () => setOutputAudioId(deviceId.toDartString()); } + +/// Subscribes onto the `MediaDevices`'s `devicechange` event. +void _onDeviceChange(Function cb) { + onDeviceChange(() => cb(null)); +} diff --git a/flutter/lib/src/native/platform/media_devices.g.dart b/flutter/lib/src/native/platform/media_devices.g.dart index 0c38e447e..2f6627b55 100644 --- a/flutter/lib/src/native/platform/media_devices.g.dart +++ b/flutter/lib/src/native/platform/media_devices.g.dart @@ -9,14 +9,16 @@ void registerFunction( required Pointer> getDisplayMedia, required Pointer)>> setOutputAudioId, + required Pointer> onDeviceChange, }) { dl.lookupFunction< - Void Function(Pointer, Pointer, Pointer, Pointer), - void Function( - Pointer, Pointer, Pointer, Pointer)>('register_media_devices')( + Void Function(Pointer, Pointer, Pointer, Pointer, Pointer), + void Function(Pointer, Pointer, Pointer, Pointer, + Pointer)>('register_media_devices')( enumerateDevices, getUserMedia, getDisplayMedia, setOutputAudioId, + onDeviceChange, ); } diff --git a/flutter/lib/src/web/jason_wasm.dart b/flutter/lib/src/web/jason_wasm.dart index 8280c792f..24df57ff7 100644 --- a/flutter/lib/src/web/jason_wasm.dart +++ b/flutter/lib/src/web/jason_wasm.dart @@ -136,6 +136,7 @@ class LocalMediaTrack { @JS() class MediaManagerHandle { + external void on_device_change(Function cb); external void free(); } diff --git a/flutter/lib/src/web/media_manager.dart b/flutter/lib/src/web/media_manager.dart index 8344058f4..a5e05412c 100644 --- a/flutter/lib/src/web/media_manager.dart +++ b/flutter/lib/src/web/media_manager.dart @@ -41,4 +41,9 @@ class WebMediaManagerHandle extends MediaManagerHandle { Future setOutputAudioId(String deviceId) async { video_renderer.setOutputAudioSinkId(deviceId); } + + @override + void onDeviceChange(Function cb) { + obj.on_device_change(cb); + } } diff --git a/src/api/dart/media_manager_handle.rs b/src/api/dart/media_manager_handle.rs index bdfa490c5..77a68cf77 100644 --- a/src/api/dart/media_manager_handle.rs +++ b/src/api/dart/media_manager_handle.rs @@ -1,13 +1,18 @@ use std::{os::raw::c_char, ptr}; +use dart_sys::Dart_Handle; use tracerr::Traced; use crate::{ - api::c_str_into_string, + api::{ + c_str_into_string, + utils::{DartError, DartResult}, + }, media::{ EnumerateDevicesError, InitLocalTracksError, InvalidOutputAudioDeviceIdError, }, + platform, }; use super::{ @@ -76,6 +81,18 @@ pub unsafe extern "C" fn MediaManagerHandle__set_output_audio_id( .into_dart_future() } +/// Subscribes onto the [`MediaManagerHandle`]'s `devicechange` event. +#[no_mangle] +pub unsafe extern "C" fn MediaManagerHandle__on_device_change( + this: ptr::NonNull, + cb: Dart_Handle, +) -> DartResult { + let this = this.as_ref(); + this.on_device_change(platform::Function::new(cb)) + .map_err(DartError::from) + .into() +} + /// Frees the data behind the provided pointer. /// /// # Safety @@ -104,7 +121,7 @@ mod mock { LocalMediaTrack, MediaDeviceInfo, MediaStreamSettings, }, media::{ - EnumerateDevicesError, InitLocalTracksError, + EnumerateDevicesError, HandleDetachedError, InitLocalTracksError, InvalidOutputAudioDeviceIdError, }, platform, @@ -144,6 +161,14 @@ mod mock { ) -> Result<(), Traced> { Ok(()) } + + pub fn on_device_change( + &self, + cb: platform::Function<()>, + ) -> Result<(), Traced> { + cb.call0(); + Ok(()) + } } #[no_mangle] diff --git a/src/api/err.rs b/src/api/err.rs index 8da372aa6..ab00b1c05 100644 --- a/src/api/err.rs +++ b/src/api/err.rs @@ -14,7 +14,7 @@ use crate::{ api::Error, connection, media::{ - EnumerateDevicesError, GetDisplayMediaError, GetUserMediaError, + self, EnumerateDevicesError, GetDisplayMediaError, GetUserMediaError, InitLocalTracksError, InvalidOutputAudioDeviceIdError, }, peer::{ @@ -485,6 +485,13 @@ impl MediaSettingsUpdateException { } } +impl From> for Error { + fn from(err: Traced) -> Self { + let (err, trace) = err.split(); + StateError::new(err.to_string(), trace).into() + } +} + impl From> for Error { fn from(err: Traced) -> Self { let (err, trace) = err.split(); @@ -502,7 +509,14 @@ impl From> for Error { impl From> for Error { fn from(err: Traced) -> Self { let (err, stacktrace) = err.split(); - EnumerateDevicesException::new(err.into(), stacktrace).into() + match err { + EnumerateDevicesError::Failed(err) => { + EnumerateDevicesException::new(err, stacktrace).into() + } + EnumerateDevicesError::Detached => { + StateError::new(err.to_string(), stacktrace).into() + } + } } } diff --git a/src/api/wasm/media_manager_handle.rs b/src/api/wasm/media_manager_handle.rs index d63ffd696..aec3ec164 100644 --- a/src/api/wasm/media_manager_handle.rs +++ b/src/api/wasm/media_manager_handle.rs @@ -109,4 +109,15 @@ impl MediaManagerHandle { .map_err(Into::into) }) } + + /// Subscribes onto the [`MediaManagerHandle`]'s `devicechange` event. + pub fn on_device_change( + &self, + cb: js_sys::Function, + ) -> Result<(), JsValue> { + let this = self.0.clone(); + this.on_device_change(cb.into()) + .map_err(Error::from) + .map_err(Into::into) + } } diff --git a/src/media/manager.rs b/src/media/manager.rs index 94e9e1594..e05dc23f4 100644 --- a/src/media/manager.rs +++ b/src/media/manager.rs @@ -6,7 +6,7 @@ use std::{ rc::{Rc, Weak}, }; -use derive_more::{Display, From, Into}; +use derive_more::{Display, From}; use medea_client_api_proto::MediaSourceKind; use tracerr::Traced; @@ -22,10 +22,17 @@ use crate::{ use super::track::local; /// Errors returned from the [`MediaManagerHandle::enumerate_devices()`] method. -#[derive(Caused, Clone, Debug, Display, From, Into)] +#[derive(Caused, Clone, Debug, Display, From)] #[cause(error = "platform::Error")] -#[display(fmt = "MediaDevices.enumerateDevices() failed: {}", _0)] -pub struct EnumerateDevicesError(platform::Error); +pub enum EnumerateDevicesError { + /// Occurs if the `enumerateDevices` request fails. + #[display(fmt = "MediaDevices.enumerateDevices() failed: {}", _0)] + Failed(platform::Error), + + /// [`MediaManagerHandle`]'s inner [`Weak`] pointer cannot be upgraded. + #[display(fmt = "MediaManagerHandle is in detached state")] + Detached, +} /// Errors returned from the [`MediaManagerHandle::init_local_tracks()`] method. #[derive(Caused, Clone, Debug, Display, From)] @@ -55,6 +62,11 @@ pub enum InitLocalTracksError { #[display(fmt = "Invalid audio device ID provided")] pub struct InvalidOutputAudioDeviceIdError; +/// Error indicating about a [`MediaManagerHandle`] in detached state. +#[derive(Clone, Copy, Debug, Display)] +#[display(fmt = "MediaManagerHandle is in detached state")] +pub struct HandleDetachedError; + /// Error occurring when [`local::Track`] was [`ended`][1] right after /// [getUserMedia()][2] or [getDisplayMedia()][3] request. /// @@ -139,13 +151,25 @@ pub struct MediaManager(Rc); struct InnerMediaManager { /// Obtained tracks storage tracks: Rc>>>, + + /// Media devices platform controller. + media_devices: platform::MediaDevices, } impl InnerMediaManager { + /// Subscribes onto the `devicechange` event of this [`InnerMediaManager`]. + pub fn on_device_change(&self, cb: platform::Function<()>) { + self.media_devices.on_device_change(Some(move || { + cb.call0(); + })); + } + /// Returns a list of [`platform::MediaDeviceInfo`] objects. async fn enumerate_devices( + &self, ) -> Result, Traced> { - platform::enumerate_devices() + self.media_devices + .enumerate_devices() .await .map_err(tracerr::wrap!()) } @@ -278,7 +302,9 @@ impl InnerMediaManager { &self, caps: platform::MediaStreamConstraints, ) -> Result>, Traced> { - let tracks = platform::get_user_media(caps) + let tracks = self + .media_devices + .get_user_media(caps) .await .map_err(tracerr::map_from_and_wrap!())?; @@ -298,7 +324,9 @@ impl InnerMediaManager { &self, caps: platform::DisplayMediaStreamConstraints, ) -> Result>, Traced> { - let tracks = platform::get_display_media(caps) + let tracks = self + .media_devices + .get_display_media(caps) .await .map_err(tracerr::map_from_and_wrap!())?; @@ -362,7 +390,8 @@ impl InnerMediaManager { device_id: String, ) -> Result<(), Traced> { #[allow(clippy::map_err_ignore)] - platform::set_output_audio_id(device_id) + self.media_devices + .set_output_audio_id(device_id) .await .map_err(|_| tracerr::new!(InvalidOutputAudioDeviceIdError)) } @@ -428,7 +457,11 @@ impl MediaManagerHandle { &self, ) -> Result, Traced> { - InnerMediaManager::enumerate_devices() + let this = self + .0 + .upgrade() + .ok_or_else(|| tracerr::new!(EnumerateDevicesError::Detached))?; + this.enumerate_devices() .await .map_err(tracerr::map_from_and_wrap!()) } @@ -480,4 +513,21 @@ impl MediaManagerHandle { .await .map_err(tracerr::map_from_and_wrap!()) } + + /// Subscribes onto the `devicechange` event of this [`MediaManagerHandle`]. + /// + /// # Errors + /// + /// If the underlying [`MediaManagerHandle`] is dropped. + pub fn on_device_change( + &self, + cb: platform::Function<()>, + ) -> Result<(), Traced> { + let this = self + .0 + .upgrade() + .ok_or_else(|| tracerr::new!(HandleDetachedError))?; + this.on_device_change(cb); + Ok(()) + } } diff --git a/src/media/mod.rs b/src/media/mod.rs index dd872941f..eaec3ce31 100644 --- a/src/media/mod.rs +++ b/src/media/mod.rs @@ -19,8 +19,8 @@ pub use self::{ }, manager::{ EnumerateDevicesError, GetDisplayMediaError, GetUserMediaError, - InitLocalTracksError, InvalidOutputAudioDeviceIdError, MediaManager, - MediaManagerHandle, + HandleDetachedError, InitLocalTracksError, + InvalidOutputAudioDeviceIdError, MediaManager, MediaManagerHandle, }, track::MediaSourceKind, }; diff --git a/src/platform/dart/media_devices.rs b/src/platform/dart/media_devices.rs index 2a33dcfbe..adc72491f 100644 --- a/src/platform/dart/media_devices.rs +++ b/src/platform/dart/media_devices.rs @@ -11,6 +11,7 @@ use crate::{ dart::utils::{ dart_future::FutureFromDart, handle::DartHandle, list::DartList, }, + utils::callback::Callback, Error, }, }; @@ -51,101 +52,131 @@ mod media_devices { pub fn set_output_audio_id( device_id: ptr::NonNull, ) -> Dart_Handle; + + /// Subscribes onto the `MediaDevices`'s `devicechange` event. + pub fn on_device_change(cb: Dart_Handle); } } -/// Collects information about available media input devices. -/// -/// Adapter for a [MediaDevices.enumerateDevices()][1] function. -/// -/// # Errors -/// -/// 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 -pub async fn enumerate_devices() -> Result, Traced> -{ - 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); +/// Media devices controller. +#[derive(Clone, Copy, Debug, Default)] +pub struct MediaDevices; + +impl MediaDevices { + /// Collects information about available media input devices. + /// + /// Adapter for the [MediaDevices.enumerateDevices()][1] function. + /// + /// # Errors + /// + /// 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 + pub async fn enumerate_devices( + &self, + ) -> Result, Traced> { + 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) } - Ok(result) -} -/// 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 -/// -/// 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 -pub async fn get_user_media( - caps: MediaStreamConstraints, -) -> Result, Traced> { - 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 for permissions to use a media input device, producing + /// [`MediaStreamTrack`]s containing the requested types of media. + /// + /// Adapter for the [MediaDevices.getUserMedia()][1] function. + /// + /// # Errors + /// + /// If [MediaDevices.getUserMedia()][1] errors itself or unable to get + /// [MediaDevices][2]. + /// + /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia + /// [2]: https://w3.org/TR/mediacapture-streams#mediadevices + pub async fn get_user_media( + &self, + caps: MediaStreamConstraints, + ) -> Result, Traced> { + 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 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 -/// -/// If [MediaDevices.getDisplayMedia()][1] errors itself or unable to get -/// [MediaDevices][2]. -/// -/// [1]: https://w3.org/TR/screen-capture#dom-mediadevices-getdisplaymedia -/// [2]: https://w3.org/TR/mediacapture-streams#mediadevices -pub async fn get_display_media( - caps: DisplayMediaStreamConstraints, -) -> Result, Traced> { - let tracks = FutureFromDart::execute::(unsafe { - media_devices::get_display_media(caps.into()) - }) - .await - .map_err(tracerr::wrap!())?; - - Ok(DartList::from(tracks).into()) -} + /// Prompts a user to select and grant permissions to capture contents of a + /// display or portion thereof (such as a single window), producing + /// [`MediaStreamTrack`]s containing the requested types of media. + /// + /// Adapter for a [MediaDevices.getDisplayMedia()][1] function. + /// + /// # Errors + /// + /// If [MediaDevices.getDisplayMedia()][1] errors itself or unable to get + /// [MediaDevices][2]. + /// + /// [1]: https://w3.org/TR/screen-capture#dom-mediadevices-getdisplaymedia + /// [2]: https://w3.org/TR/mediacapture-streams#mediadevices + pub async fn get_display_media( + &self, + caps: DisplayMediaStreamConstraints, + ) -> Result, Traced> { + let tracks = FutureFromDart::execute::(unsafe { + media_devices::get_display_media(caps.into()) + }) + .await + .map_err(tracerr::wrap!())?; + + Ok(DartList::from(tracks).into()) + } + + /// Switches the current output audio device to the device with the provided + /// `device_id`. + /// + /// # Errors + /// + /// If output audio device with the provided `device_id` is not available. + pub async fn set_output_audio_id( + &self, + device_id: String, + ) -> Result<(), Traced> { + FutureFromDart::execute::<()>(unsafe { + media_devices::set_output_audio_id(string_into_c_str(device_id)) + }) + .await + .map_err(tracerr::wrap!()) + } -/// Switches the current output audio device to the device with the provided -/// `device_id`. -/// -/// # Errors -/// -/// If output audio device with the provided `device_id` is not available. -pub async fn set_output_audio_id( - device_id: String, -) -> Result<(), Traced> { - FutureFromDart::execute::<()>(unsafe { - media_devices::set_output_audio_id(string_into_c_str(device_id)) - }) - .await - .map_err(tracerr::wrap!()) + /// Subscribes onto the [`MediaDevices`]'s `devicechange` event. + pub fn on_device_change(&self, handler: Option) + where + F: 'static + FnMut(), + { + if let Some(mut h) = handler { + unsafe { + media_devices::on_device_change( + Callback::from_fn_mut(move |_: ()| { + h(); + }) + .into_dart(), + ); + }; + } + } } diff --git a/src/platform/dart/mod.rs b/src/platform/dart/mod.rs index 3e091818d..7fd26705c 100644 --- a/src/platform/dart/mod.rs +++ b/src/platform/dart/mod.rs @@ -29,10 +29,7 @@ pub use self::{ error::Error, executor::spawn, media_device_info::MediaDeviceInfo, - media_devices::{ - enumerate_devices, get_display_media, get_user_media, - set_output_audio_id, - }, + media_devices::MediaDevices, media_track::MediaStreamTrack, peer_connection::RtcPeerConnection, rtc_stats::RtcStats, diff --git a/src/platform/wasm/media_devices.rs b/src/platform/wasm/media_devices.rs index 81788cdde..5b181838f 100644 --- a/src/platform/wasm/media_devices.rs +++ b/src/platform/wasm/media_devices.rs @@ -2,168 +2,226 @@ //! //! [1]: https://w3.org/TR/mediacapture-streams#mediadevices +use std::{cell::RefCell, rc::Rc}; use wasm_bindgen_futures::JsFuture; use tracerr::Traced; +use web_sys::{Event, MediaDevices as SysMediaDevices}; use crate::{ media::InvalidOutputAudioDeviceIdError, platform::{ - DisplayMediaStreamConstraints, Error, MediaDeviceInfo, - MediaStreamConstraints, MediaStreamTrack, + utils::EventListener, DisplayMediaStreamConstraints, Error, + MediaDeviceInfo, MediaStreamConstraints, MediaStreamTrack, }, }; use super::window; -/// Collects information about the User Agent's available media input devices. -/// -/// Adapter for a [MediaDevices.enumerateDevices()][1] function. -/// -/// # Errors -/// -/// With [`Error`] if [MediaDevices.enumerateDevices()][1] returns error or -/// cannot get [MediaDevices][2]. -/// -/// # Panics -/// -/// If [`js_sys::Array`] returned from [MediaDevices.enumerateDevices()][1] -/// contains something that is not [`web_sys::MediaDeviceInfo`]. -/// -/// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-enumeratedevices -/// [2]: https://w3.org/TR/mediacapture-streams#mediadevices -pub async fn enumerate_devices() -> Result, Traced> -{ - let devices = window() - .navigator() - .media_devices() +/// Media devices controller. +#[derive(Debug)] +pub struct MediaDevices { + /// Underlying [`SysMediaDevices`], used for media devices management. + devices: Rc, + + /// [`EventListener`] for the `devicechange` event of the underlying + /// [`SysMediaDevices`]. + on_device_change_listener: + RefCell>>, +} + +impl Default for MediaDevices { + fn default() -> Self { + Self::new() + } +} + +impl MediaDevices { + /// Returns new [`MediaDevices`]. + /// + /// # Panics + /// + /// If failed to get [`SysMediaDevices`] from the browser. + #[must_use] + pub fn new() -> Self { + let devices = window() + .navigator() + .media_devices() + .map_err(Error::from) + .map_err(tracerr::wrap!()) + .unwrap(); + Self { + devices: Rc::new(devices), + on_device_change_listener: RefCell::new(None), + } + } + + /// Collects information about the User Agent's available media input + /// devices. + /// + /// Adapter for the [MediaDevices.enumerateDevices()][1] function. + /// + /// # Errors + /// + /// With [`Error`] if [MediaDevices.enumerateDevices()][1] returns error or + /// cannot get [MediaDevices][2]. + /// + /// # Panics + /// + /// If [`js_sys::Array`] returned from [MediaDevices.enumerateDevices()][1] + /// contains something that is not [`web_sys::MediaDeviceInfo`]. + /// + /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-enumeratedevices + /// [2]: https://w3.org/TR/mediacapture-streams#mediadevices + pub async fn enumerate_devices( + &self, + ) -> Result, Traced> { + let devices = JsFuture::from( + self.devices + .enumerate_devices() + .map_err(Error::from) + .map_err(tracerr::wrap!())?, + ) + .await .map_err(Error::from) .map_err(tracerr::wrap!())?; - let devices = JsFuture::from( - devices - .enumerate_devices() - .map_err(Error::from) - .map_err(tracerr::wrap!())?, - ) - .await - .map_err(Error::from) - .map_err(tracerr::wrap!())?; - - Ok(js_sys::Array::from(&devices) - .values() - .into_iter() - .map(|info| { - let info = web_sys::MediaDeviceInfo::from(info.unwrap()); - MediaDeviceInfo::from(info) - }) - .collect()) -} -/// Prompts a user for a permission to use a media input which produces vector -/// of [`MediaStreamTrack`]s containing the requested types of media. -/// -/// Adapter for a [MediaDevices.getUserMedia()][1] function. -/// -/// # Errors -/// -/// With [`Error`] if [MediaDevices.getUserMedia()][1] returns error or cannot -/// get [MediaDevices][2]. -/// -/// # Panics -/// -/// If [`js_sys::Array`] returned from [MediaDevices.getUserMedia()][1] -/// contains something that is not [`web_sys::MediaStreamTrack`]. -/// -/// [1]: https://w3.org/TR/mediacapture-streams#dom-mediadevices-getusermedia -/// [2]: https://w3.org/TR/mediacapture-streams#mediadevices -pub async fn get_user_media( - caps: MediaStreamConstraints, -) -> Result, Traced> { - let media_devices = window() - .navigator() - .media_devices() + Ok(js_sys::Array::from(&devices) + .values() + .into_iter() + .map(|info| { + let info = web_sys::MediaDeviceInfo::from(info.unwrap()); + MediaDeviceInfo::from(info) + }) + .collect()) + } + + /// Prompts a user for a permission to use a media input which produces + /// [`MediaStreamTrack`]s containing the requested types of media. + /// + /// Adapter for the [MediaDevices.getUserMedia()][1] function. + /// + /// # Errors + /// + /// With [`Error`] if [MediaDevices.getUserMedia()][1] returns error or + /// cannot get [MediaDevices][2]. + /// + /// # Panics + /// + /// If [`js_sys::Array`] returned from [MediaDevices.getUserMedia()][1] + /// contains something that is not [`web_sys::MediaStreamTrack`]. + /// + /// [1]: https://tinyurl.com/w3-streams#dom-mediadevices-getusermedia + /// [2]: https://w3.org/TR/mediacapture-streams#mediadevices + pub async fn get_user_media( + &self, + caps: MediaStreamConstraints, + ) -> Result, Traced> { + let stream = JsFuture::from( + self.devices + .get_user_media_with_constraints(&caps.into()) + .map_err(Error::from) + .map_err(tracerr::wrap!())?, + ) + .await + .map(web_sys::MediaStream::from) .map_err(Error::from) .map_err(tracerr::wrap!())?; - let stream = JsFuture::from( - media_devices - .get_user_media_with_constraints(&caps.into()) + Ok(js_sys::try_iter(&stream.get_tracks()) + .unwrap() + .unwrap() + .map(|tr| MediaStreamTrack::from(tr.unwrap())) + .collect()) + } + + /// 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`]s. + /// + /// Adapter for the [MediaDevices.getDisplayMedia()][1] function. + /// + /// # Errors + /// + /// With [`Error`] if [MediaDevices.getDisplayMedia()][1] returns error or + /// cannot get [MediaDevices][2]. + /// + /// # Panics + /// + /// If [`js_sys::Array`] returned from [MediaDevices.getDisplayMedia()][1] + /// contains something that is not [`web_sys::MediaStreamTrack`]. + /// + /// [1]: https://w3.org/TR/screen-capture#dom-mediadevices-getdisplaymedia + /// [2]: https://w3.org/TR/mediacapture-streams#mediadevices + pub async fn get_display_media( + &self, + caps: DisplayMediaStreamConstraints, + ) -> Result, Traced> { + let media_devices = window() + .navigator() + .media_devices() .map_err(Error::from) - .map_err(tracerr::wrap!())?, - ) - .await - .map(web_sys::MediaStream::from) - .map_err(Error::from) - .map_err(tracerr::wrap!())?; - - Ok(js_sys::try_iter(&stream.get_tracks()) - .unwrap() - .unwrap() - .map(|tr| MediaStreamTrack::from(tr.unwrap())) - .collect()) -} + .map_err(tracerr::wrap!())?; -/// 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`]. -/// -/// Adapter for a [MediaDevices.getDisplayMedia()][1] function. -/// -/// # Errors -/// -/// With [`Error`] if [MediaDevices.getDisplayMedia()][1] returns error or -/// cannot get [MediaDevices][2]. -/// -/// # Panics -/// -/// If [`js_sys::Array`] returned from [MediaDevices.getDisplayMedia()][1] -/// contains something that is not [`web_sys::MediaStreamTrack`]. -/// -/// [1]: https://w3.org/TR/screen-capture#dom-mediadevices-getdisplaymedia -/// [2]: https://w3.org/TR/mediacapture-streams#mediadevices -pub async fn get_display_media( - caps: DisplayMediaStreamConstraints, -) -> Result, Traced> { - let media_devices = window() - .navigator() - .media_devices() + let stream = JsFuture::from( + media_devices + .get_display_media_with_constraints(&caps.into()) + .map_err(Error::from) + .map_err(tracerr::wrap!())?, + ) + .await + .map(web_sys::MediaStream::from) .map_err(Error::from) .map_err(tracerr::wrap!())?; - let stream = JsFuture::from( - media_devices - .get_display_media_with_constraints(&caps.into()) - .map_err(Error::from) - .map_err(tracerr::wrap!())?, - ) - .await - .map(web_sys::MediaStream::from) - .map_err(Error::from) - .map_err(tracerr::wrap!())?; - - Ok(js_sys::try_iter(&stream.get_tracks()) - .unwrap() - .unwrap() - .map(|tr| MediaStreamTrack::from(tr.unwrap())) - .collect()) -} + Ok(js_sys::try_iter(&stream.get_tracks()) + .unwrap() + .unwrap() + .map(|tr| MediaStreamTrack::from(tr.unwrap())) + .collect()) + } -/// This method should be unreachable, because this functional is implemented on -/// the Dart side of Jason only. -/// -/// # Errors -/// -/// Never. -/// -/// # Panics -/// -/// Always. -#[allow(clippy::unused_async)] -pub async fn set_output_audio_id( - _: String, -) -> Result<(), Traced> { - unreachable!( - "`set_output_audio_id()` is implemented on the Dart side,\ + /// This method should be unreachable, because this functional is + /// implemented on the Dart side of Jason only. + /// + /// # Errors + /// + /// Never. + /// + /// # Panics + /// + /// Always. + #[allow(clippy::unused_async)] + pub async fn set_output_audio_id( + &self, + _: String, + ) -> Result<(), Traced> { + unreachable!( + "`set_output_audio_id()` is implemented on the Dart side,\ so this method call is unreachable", - ) + ) + } + + /// Subscribes onto the [`MediaDevices`]'s `devicechange` event. + /// + /// # Panics + /// + /// If `devicechange` event listener binding fails. + pub fn on_device_change(&self, f: Option) + where + F: 'static + FnMut(), + { + if let Some(mut f) = f { + drop( + self.on_device_change_listener.borrow_mut().replace( + EventListener::new_mut( + Rc::clone(&self.devices), + "devicechange", + move |_| f(), + ) + .unwrap(), + ), + ); + } + } } diff --git a/src/platform/wasm/mod.rs b/src/platform/wasm/mod.rs index b6ea64d64..e7b0814dc 100644 --- a/src/platform/wasm/mod.rs +++ b/src/platform/wasm/mod.rs @@ -24,10 +24,7 @@ pub use self::{ constraints::{DisplayMediaStreamConstraints, MediaStreamConstraints}, error::Error, media_device_info::MediaDeviceInfo, - media_devices::{ - enumerate_devices, get_display_media, get_user_media, - set_output_audio_id, - }, + media_devices::MediaDevices, media_track::MediaStreamTrack, peer_connection::RtcPeerConnection, rtc_stats::RtcStats,