Add hardware api for camera, audio input/output selection. (#147)
* chore: Add hardware api for device settings dialog, camera preview, audio input/output selection. * update. * update flutter-webrtc. * chore: add popupmenu for audio/video selection. * chore: Improve menu tooltip text. * Removed hardware settings dialog, added it to experimental branch. * remove unused code. * Respond to device changes. * fixed `flutter analyze`. * update. * bump verion to v1.1.1.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
|
||||
|
||||
class MediaDevice {
|
||||
MediaDevice(this.deviceId, this.label, this.kind);
|
||||
|
||||
final String deviceId;
|
||||
final String label;
|
||||
final String kind;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MediaDevice{deviceId: $deviceId, label: $label, kind: $kind}';
|
||||
}
|
||||
}
|
||||
|
||||
class Hardware {
|
||||
Hardware._internal() {
|
||||
rtc.navigator.mediaDevices.ondevicechange = _onDeviceChange;
|
||||
enumerateDevices().then((devices) {
|
||||
selectedAudioInput ??=
|
||||
devices.where((element) => element.kind == 'audioinput').first;
|
||||
selectedAudioOutput ??=
|
||||
devices.where((element) => element.kind == 'audiooutput').first;
|
||||
selectedVideoInput ??=
|
||||
devices.where((element) => element.kind == 'videoinput').first;
|
||||
});
|
||||
}
|
||||
|
||||
static final Hardware instance = Hardware._internal();
|
||||
|
||||
final StreamController<List<MediaDevice>> onDeviceChange =
|
||||
StreamController.broadcast();
|
||||
|
||||
MediaDevice? selectedAudioInput;
|
||||
|
||||
MediaDevice? selectedAudioOutput;
|
||||
|
||||
MediaDevice? selectedVideoInput;
|
||||
|
||||
Future<List<MediaDevice>> enumerateDevices({String? type}) async {
|
||||
var infos = await rtc.navigator.mediaDevices.enumerateDevices();
|
||||
var devices =
|
||||
infos.map((e) => MediaDevice(e.deviceId, e.label, e.kind!)).toList();
|
||||
if (type != null && type.isNotEmpty) {
|
||||
devices = devices.where((d) => d.kind == type).toList();
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
Future<List<MediaDevice>> audioInputs() async {
|
||||
return enumerateDevices(type: 'audioinput');
|
||||
}
|
||||
|
||||
Future<List<MediaDevice>> audioOutputs() async {
|
||||
return enumerateDevices(type: 'audiooutput');
|
||||
}
|
||||
|
||||
Future<List<MediaDevice>> videoInputs() async {
|
||||
return enumerateDevices(type: 'videoinput');
|
||||
}
|
||||
|
||||
Future<void> selectAudioOutput(MediaDevice device) async {
|
||||
if (rtc.WebRTC.platformIsWeb) {
|
||||
throw UnimplementedError('selectAudioOutput not support on web');
|
||||
}
|
||||
selectedAudioOutput = device;
|
||||
await rtc.Helper.selectAudioOutput(device.deviceId);
|
||||
}
|
||||
|
||||
Future<void> selectAudioInput(MediaDevice device) async {
|
||||
if (rtc.WebRTC.platformIsWeb || rtc.WebRTC.platformIsIOS) {
|
||||
throw UnimplementedError(
|
||||
'selectAudioInput is only supported on Android/Windows/macOS');
|
||||
}
|
||||
selectedAudioInput = device;
|
||||
await rtc.Helper.selectAudioInput(device.deviceId);
|
||||
}
|
||||
|
||||
Future<void> setSpeakerphoneOn(bool enable) async {
|
||||
if (!rtc.WebRTC.platformIsAndroid || !rtc.WebRTC.platformIsIOS) {
|
||||
throw UnimplementedError('setSpeakerphoneOn only support on iOS/Android');
|
||||
}
|
||||
await rtc.Helper.setSpeakerphoneOn(enable);
|
||||
}
|
||||
|
||||
Future<rtc.MediaStream> openCamera(
|
||||
{MediaDevice? device, bool? facingMode}) async {
|
||||
var constraints = <String, dynamic>{
|
||||
if (facingMode != null) 'facingMode': facingMode ? 'user' : 'environment',
|
||||
};
|
||||
if (device != null) {
|
||||
if (rtc.WebRTC.platformIsWeb) {
|
||||
constraints['deviceId'] = device.deviceId;
|
||||
} else {
|
||||
constraints['optional'] = [
|
||||
{'sourceId': device.deviceId}
|
||||
];
|
||||
}
|
||||
}
|
||||
selectedVideoInput = device;
|
||||
return rtc.navigator.mediaDevices.getUserMedia(<String, dynamic>{
|
||||
'audio': false,
|
||||
'video': device != null ? constraints : true,
|
||||
});
|
||||
}
|
||||
|
||||
dynamic _onDeviceChange(dynamic _) async {
|
||||
var devices = await enumerateDevices();
|
||||
selectedAudioInput ??=
|
||||
devices.where((element) => element.kind == 'audioinput').first;
|
||||
selectedAudioOutput ??=
|
||||
devices.where((element) => element.kind == 'audiooutput').first;
|
||||
selectedVideoInput ??=
|
||||
devices.where((element) => element.kind == 'videoinput').first;
|
||||
onDeviceChange.add(devices);
|
||||
}
|
||||
}
|
||||
@@ -81,4 +81,16 @@ extension LocalVideoTrackExt on LocalVideoTrack {
|
||||
options.copyWith(cameraPosition: position),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> switchCamera(String deviceId) async {
|
||||
final options = currentOptions;
|
||||
if (options is! CameraCaptureOptions) {
|
||||
logger.warning('Not a camera track');
|
||||
return;
|
||||
}
|
||||
|
||||
await restartTrack(
|
||||
options.copyWith(deviceId: deviceId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user