Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LocalMediaTrack.on_audio_level_changed() callback #167

Merged
merged 21 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ All user visible changes to this project will be documented in this file. This p
- `IceCandidateError` metric sending to server ([#151]);
- `transport_id`, `local_candidate_id` and `remote_candidate_id` to `RtcIceCandidatePairStats` ([#172]).
- Library API:
- `ideal_auto_gain_control()` and `exact_auto_gain_control()` methods to `AudioTrackConstraints` ([#166]).
- `ideal_auto_gain_control()` and `exact_auto_gain_control()` methods to `AudioTrackConstraints` ([#166]);
- `is_on_audio_level_available()` and `on_audio_level_changed()` methods to `LocalMediaTrack` ([#167]).

### Fixed

Expand All @@ -39,6 +40,7 @@ All user visible changes to this project will be documented in this file. This p
[#162]: /../../pull/162
[#163]: /../../pull/163
[#166]: /../../pull/166
[#167]: /../../pull/167
[#172]: /../../pull/172


Expand Down
14 changes: 14 additions & 0 deletions flutter/example/lib/call.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Call {
/// Callback for creating/changing a render from a local video display track.
late Function(webrtc.MediaStreamTrack) _onLocalDisplayTrack;

/// Callback which will be called once new local audio track is added.
late Function(LocalMediaTrack) _onLocalAudioTrack;

/// Errors handler.
Function(String) _onError = (p0) {};

Expand Down Expand Up @@ -101,6 +104,8 @@ class Call {
for (var track in tracks) {
if (track.kind() == MediaKind.video) {
_onLocalDeviceTrack(track.getTrack());
} else if (track.kind() == MediaKind.audio) {
_onLocalAudioTrack(track);
}
}

Expand All @@ -112,6 +117,8 @@ class Call {
} else {
_onLocalDisplayTrack(track.getTrack());
}
} else if (track.kind() == MediaKind.audio) {
_onLocalAudioTrack(track);
}
});

Expand Down Expand Up @@ -159,6 +166,8 @@ class Call {
} else {
_onLocalDeviceTrack(track.getTrack());
}
} else if (track.kind() == MediaKind.audio) {
_onLocalAudioTrack(track);
}
}
}
Expand All @@ -172,6 +181,11 @@ class Call {
_jason.closeRoom(_room);
}

/// Sets the callback for a new local audio track.
void onLocalAudioTrack(Function(LocalMediaTrack) f) {
_onLocalAudioTrack = f;
}

/// Sets the callback for a new local device track.
void onLocalDeviceStream(Function(webrtc.MediaStreamTrack) f) {
_onLocalDeviceTrack = f;
Expand Down
43 changes: 34 additions & 9 deletions flutter/example/lib/call_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class _CallState extends State<CallRoute> {
VideoView? localScreenVideo;
VideoView? localCameraVideo;

double currentAudioLevel = 0.0;

final Map<String, ConnectWidgets> _widgets = {};

final Call _call = Call();
Expand Down Expand Up @@ -171,6 +173,14 @@ class _CallState extends State<CallRoute> {
);
});

_call.onLocalAudioTrack((track) {
track.onAudioLevelChanged((volume) {
setState(() {
currentAudioLevel = volume / 100;
});
});
});

_call.onLocalDeviceStream((track) async {
if (localCameraVideo == null) {
var renderer = createVideoRenderer();
Expand Down Expand Up @@ -281,15 +291,30 @@ class _CallState extends State<CallRoute> {
}),
]),
body: Center(
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Row(
children: _widgets.values
.map((videoMap) => Expanded(
child: Column(children: videoMap.all().toList())))
.toList(),
))),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
children: [
LinearProgressIndicator(
value: currentAudioLevel,
minHeight: 10.0,
),
Expanded(
child: Row(
children: _widgets.values
.map((videoMap) => Expanded(
child: Column(
children: videoMap.all().toList(),
),
))
.toList(),
),
)
],
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: Padding(
padding: const EdgeInsets.only(bottom: 50.0),
Expand Down
14 changes: 14 additions & 0 deletions flutter/lib/src/interface/media_track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ typedef TrackMediaDirection = MediaDirection;
/// Representation of the `onEnded` callback.
typedef OnEndedCallback = void Function();

/// Representation of an `onAudioLevelChanged` callback.
///
/// The provided values will be in [0; 100] range.
typedef OnAudioLevelChangedCallback = void Function(int);

/// Abstraction of a handle to an object allocated on the Rust side.
abstract class MediaTrack implements AsyncPlatformHandle {
/// Returns the [MediaKind.Audio] if this [LocalMediaTrack] represents an
Expand Down Expand Up @@ -40,6 +45,15 @@ abstract class LocalMediaTrack implements MediaTrack {
/// This only works on Web.
void onEnded(OnEndedCallback f);

/// Indicates whether an [OnAudioLevelChangedCallback] is supported for this
/// [MediaTrack].
bool isOnAudioLevelAvailable();

/// Sets the provided [OnAudioLevelChangedCallback] for this [MediaTrack].
///
/// It's called for live [MediaTrack]s when their audio level changes.
void onAudioLevelChanged(OnAudioLevelChangedCallback f);

/// Returns a [MediaStreamTrackState.live] if this [LocalMediaTrack] is
/// active, or a [MediaStreamTrackState.ended] if it has ended.
Future<MediaStreamTrackState> state();
Expand Down
14 changes: 10 additions & 4 deletions flutter/lib/src/native/ffi/function.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ void _callFn(Object fn, ForeignValue value) {
try {
var arg = value.toDart();
if (arg != null) {
var res = (fn as dynamic Function(dynamic))(arg);
if (res is Future<void>) {
res.catchError((e, stack) => api.logDartException(
message: e.toString(), stackTrace: stack.toString()));
if (fn is dynamic Function(dynamic)) {
var res = fn(arg);
if (res is Future<void>) {
res.catchError((e, stack) => api.logDartException(
message: e.toString(), stackTrace: stack.toString()));
}
} else if (fn is void Function(int)) {
fn(arg);
} else {
throw 'Unknown Function signature, this typecast needs to be extended';
}
} else {
var res = (fn as dynamic Function())();
Expand Down
122 changes: 119 additions & 3 deletions flutter/lib/src/native/ffi/jason_api.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,27 @@ abstract class MedeaJason {

FlutterRustBridgeTaskConstMeta get kLocalMediaTrackStateConstMeta;

/// Indicates whether an `OnAudioLevelChangedCallback` is supported for this
/// [`LocalMediaTrack`].
bool isOnAudioLevelAvailable({required LocalMediaTrack track, dynamic hint});

FlutterRustBridgeTaskConstMeta get kIsOnAudioLevelAvailableConstMeta;

/// Sets the provided `OnAudioLevelChangedCallback` for this
/// [`LocalMediaTrack`].
///
/// It's called for live [`LocalMediaTrack`]s when their audio level changes.
void onAudioLevelChanged(
{required LocalMediaTrack track, required Object f, dynamic hint});

FlutterRustBridgeTaskConstMeta get kOnAudioLevelChangedConstMeta;

/// Returns a [`MediaSourceKind::Device`] if the provided [`LocalMediaTrack`] is
/// sourced from some device (webcam/microphone), or a
/// [`MediaSourceKind::Display`] if it's captured via
/// [MediaDevices.getDisplayMedia()][1].
///
/// [1]: https://w3.org/TR/screen-capture/#dom-mediadevices-getdisplaymedia
/// [1]: https://w3.org/TR/screen-capture#dom-mediadevices-getdisplaymedia
MediaSourceKind localMediaTrackMediaSourceKind(
{required LocalMediaTrack track, dynamic hint});

Expand Down Expand Up @@ -791,8 +806,8 @@ class ApiAudioConstraints {
/// Identifier of the device generating the content for the media track.
String? deviceId;

/// Automatically manages changes in the volume of its source
/// media to maintain a steady overall volume level.
/// Automatically manages changes in the volume of its source media to
/// maintain a steady overall volume level.
ConstrainBoolean? autoGainControl;

ApiAudioConstraints({
Expand Down Expand Up @@ -1498,6 +1513,44 @@ class MedeaJasonImpl implements MedeaJason {
argNames: ["track"],
);

bool isOnAudioLevelAvailable({required LocalMediaTrack track, dynamic hint}) {
var arg0 = _platform.api2wire_LocalMediaTrack(track);
return _platform.executeSync(FlutterRustBridgeSyncTask(
callFfi: () => _platform.inner.wire_is_on_audio_level_available(arg0),
parseSuccessData: _wire2api_bool,
parseErrorData: null,
constMeta: kIsOnAudioLevelAvailableConstMeta,
argValues: [track],
hint: hint,
));
}

FlutterRustBridgeTaskConstMeta get kIsOnAudioLevelAvailableConstMeta =>
const FlutterRustBridgeTaskConstMeta(
debugName: "is_on_audio_level_available",
argNames: ["track"],
);

void onAudioLevelChanged(
{required LocalMediaTrack track, required Object f, dynamic hint}) {
var arg0 = _platform.api2wire_LocalMediaTrack(track);
var arg1 = _platform.api2wire_DartOpaque(f);
return _platform.executeSync(FlutterRustBridgeSyncTask(
callFfi: () => _platform.inner.wire_on_audio_level_changed(arg0, arg1),
parseSuccessData: _wire2api_unit,
parseErrorData: null,
constMeta: kOnAudioLevelChangedConstMeta,
argValues: [track, f],
hint: hint,
));
}

FlutterRustBridgeTaskConstMeta get kOnAudioLevelChangedConstMeta =>
const FlutterRustBridgeTaskConstMeta(
debugName: "on_audio_level_changed",
argNames: ["track", "f"],
);

MediaSourceKind localMediaTrackMediaSourceKind(
{required LocalMediaTrack track, dynamic hint}) {
var arg0 = _platform.api2wire_LocalMediaTrack(track);
Expand Down Expand Up @@ -3796,6 +3849,39 @@ class MedeaJasonWire implements FlutterRustBridgeWireBase {
late final _wire_local_media_track_state = _wire_local_media_track_statePtr
.asFunction<WireSyncReturn Function(wire_LocalMediaTrack)>();

WireSyncReturn wire_is_on_audio_level_available(
wire_LocalMediaTrack track,
) {
return _wire_is_on_audio_level_available(
track,
);
}

late final _wire_is_on_audio_level_availablePtr = _lookup<
ffi.NativeFunction<WireSyncReturn Function(wire_LocalMediaTrack)>>(
'wire_is_on_audio_level_available');
late final _wire_is_on_audio_level_available =
_wire_is_on_audio_level_availablePtr
.asFunction<WireSyncReturn Function(wire_LocalMediaTrack)>();

WireSyncReturn wire_on_audio_level_changed(
wire_LocalMediaTrack track,
wire_DartOpaque f,
) {
return _wire_on_audio_level_changed(
track,
f,
);
}

late final _wire_on_audio_level_changedPtr = _lookup<
ffi.NativeFunction<
WireSyncReturn Function(wire_LocalMediaTrack,
wire_DartOpaque)>>('wire_on_audio_level_changed');
late final _wire_on_audio_level_changed =
_wire_on_audio_level_changedPtr.asFunction<
WireSyncReturn Function(wire_LocalMediaTrack, wire_DartOpaque)>();

WireSyncReturn wire_local_media_track_media_source_kind(
wire_LocalMediaTrack track,
) {
Expand Down Expand Up @@ -5776,6 +5862,36 @@ class MedeaJasonWire implements FlutterRustBridgeWireBase {
_lookup<ffi.NativeFunction<ffi.Handle Function(ffi.Handle)>>('dispose');
late final _dispose = _disposePtr.asFunction<Object Function(Object)>();

bool is_on_audio_level_available(
Object track,
) {
return _is_on_audio_level_available(
track,
);
}

late final _is_on_audio_level_availablePtr =
_lookup<ffi.NativeFunction<ffi.Bool Function(ffi.Handle)>>(
'is_on_audio_level_available');
late final _is_on_audio_level_available =
_is_on_audio_level_availablePtr.asFunction<bool Function(Object)>();

void on_audio_level_changed(
Object track,
Object cb,
) {
return _on_audio_level_changed(
track,
cb,
);
}

late final _on_audio_level_changedPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Handle, ffi.Handle)>>(
'on_audio_level_changed');
late final _on_audio_level_changed =
_on_audio_level_changedPtr.asFunction<void Function(Object, Object)>();

Object encodings(
Object parameters,
) {
Expand Down
10 changes: 10 additions & 0 deletions flutter/lib/src/native/local_media_track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,14 @@ class NativeLocalMediaTrack implements LocalMediaTrack {
await (api.localMediaTrackState(track: opaque.innerOpaque) as Future);
return MediaStreamTrackState.values[index];
}

@override
bool isOnAudioLevelAvailable() {
return api.isOnAudioLevelAvailable(track: opaque.innerOpaque);
}

@override
void onAudioLevelChanged(OnAudioLevelChangedCallback f) {
api.onAudioLevelChanged(track: opaque.innerOpaque, f: f);
}
}
21 changes: 21 additions & 0 deletions flutter/lib/src/native/platform/media_track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ void registerFunctions(DynamicLibrary dl) {
clone: Pointer.fromFunction(_clone),
readyState: Pointer.fromFunction(_readyState),
dispose: Pointer.fromFunction(_dispose),
onAudioLevelChanged: Pointer.fromFunction(_onAudioLevelChanged),
isOnAudioLevelAvailable:
Pointer.fromFunction(_isOnAudioLevelAvailable, false),
);
}

Expand Down Expand Up @@ -97,6 +100,24 @@ Object _stop(Object track) {
return () => track.stop();
}

/// Sets the provided [OnAudioLevelChangedCallback] for this [MediaStreamTrack].
///
/// It's called for live [MediaStreamTrack]s when their audio level changes.
void _onAudioLevelChanged(Object track, Object f) {
track as MediaStreamTrack;
f as Function;
track.onAudioLevelChanged((lvl) {
f(lvl);
});
}

/// Indicates whether a [MediaStreamTrack.onAudioLevelChanged] callback is
/// supported for this [MediaStreamTrack].
bool _isOnAudioLevelAvailable(Object track) {
track as MediaStreamTrack;
return track.isOnAudioLevelAvailable();
}

/// Indicates whether the provided [MediaStreamTrack] is enabled.
bool _enabled(Object track) {
track as MediaStreamTrack;
Expand Down
Loading
Loading