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:
@@ -1,5 +1,9 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
* Add hardware api for camera and audio input/output selection.
|
||||||
|
|
||||||
## 1.1.0-hotfix
|
## 1.1.0-hotfix
|
||||||
|
|
||||||
* Align the version in .podspec with the package version (fix compilation errors under ios/mac).
|
* Align the version in .podspec with the package version (fix compilation errors under ios/mac).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ PODS:
|
|||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_webrtc (0.9.2):
|
- flutter_webrtc (0.9.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- WebRTC-SDK (= 104.5112.02)
|
- WebRTC-SDK (= 104.5112.02)
|
||||||
- livekit_client (1.1.0):
|
- livekit_client (1.1.0):
|
||||||
@@ -43,7 +43,7 @@ EXTERNAL SOURCES:
|
|||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||||
flutter_webrtc: af1337911865d904b45c5fc95605115af4d0cdc8
|
flutter_webrtc: aa130dfe1eca6625c2e2e51ce830abb495bdb06e
|
||||||
livekit_client: f0343eef54a12a4b831d3ec1085760f13eca9ae3
|
livekit_client: f0343eef54a12a4b831d3ec1085760f13eca9ae3
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
|
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
@@ -29,20 +30,41 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
//
|
//
|
||||||
CameraPosition position = CameraPosition.front;
|
CameraPosition position = CameraPosition.front;
|
||||||
|
|
||||||
|
List<MediaDevice>? _audioInputs;
|
||||||
|
List<MediaDevice>? _audioOutputs;
|
||||||
|
List<MediaDevice>? _videoInputs;
|
||||||
|
MediaDevice? _selectedVideoInput;
|
||||||
|
|
||||||
|
StreamSubscription? _subscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
participant.addListener(_onChange);
|
participant.addListener(_onChange);
|
||||||
|
_subscription = Hardware.instance.onDeviceChange.stream
|
||||||
|
.listen((List<MediaDevice> devices) {
|
||||||
|
_loadDevices(devices);
|
||||||
|
});
|
||||||
|
Hardware.instance.enumerateDevices().then(_loadDevices);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_subscription?.cancel();
|
||||||
participant.removeListener(_onChange);
|
participant.removeListener(_onChange);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalParticipant get participant => widget.participant;
|
LocalParticipant get participant => widget.participant;
|
||||||
|
|
||||||
|
void _loadDevices(List<MediaDevice> devices) async {
|
||||||
|
_audioInputs = devices.where((d) => d.kind == 'audioinput').toList();
|
||||||
|
_audioOutputs = devices.where((d) => d.kind == 'audiooutput').toList();
|
||||||
|
_videoInputs = devices.where((d) => d.kind == 'videoinput').toList();
|
||||||
|
_selectedVideoInput = _videoInputs?.first;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
void _onChange() {
|
void _onChange() {
|
||||||
// trigger refresh
|
// trigger refresh
|
||||||
setState(() {});
|
setState(() {});
|
||||||
@@ -69,6 +91,26 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
await participant.setCameraEnabled(true);
|
await participant.setCameraEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _selectAudioOutput(MediaDevice device) async {
|
||||||
|
await Hardware.instance.selectAudioOutput(device);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _selectAudioInput(MediaDevice device) async {
|
||||||
|
await Hardware.instance.selectAudioInput(device);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _selectVideoInput(MediaDevice device) async {
|
||||||
|
final track = participant.videoTracks.firstOrNull?.track;
|
||||||
|
if (track == null) return;
|
||||||
|
if (_selectedVideoInput?.deviceId != device.deviceId) {
|
||||||
|
await track.switchCamera(device.deviceId);
|
||||||
|
_selectedVideoInput = device;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _toggleCamera() async {
|
void _toggleCamera() async {
|
||||||
//
|
//
|
||||||
final track = participant.videoTracks.firstOrNull?.track;
|
final track = participant.videoTracks.firstOrNull?.track;
|
||||||
@@ -211,10 +253,44 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
tooltip: 'Unpublish all',
|
tooltip: 'Unpublish all',
|
||||||
),
|
),
|
||||||
if (participant.isMicrophoneEnabled())
|
if (participant.isMicrophoneEnabled())
|
||||||
IconButton(
|
PopupMenuButton<MediaDevice>(
|
||||||
onPressed: _disableAudio,
|
icon: const Icon(Icons.settings_voice),
|
||||||
icon: const Icon(EvaIcons.mic),
|
itemBuilder: (BuildContext context) {
|
||||||
tooltip: 'mute audio',
|
return [
|
||||||
|
PopupMenuItem<MediaDevice>(
|
||||||
|
value: null,
|
||||||
|
child: const ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
EvaIcons.micOff,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
title: Text('Mute Microphone'),
|
||||||
|
),
|
||||||
|
onTap: _disableAudio,
|
||||||
|
),
|
||||||
|
if (_audioInputs != null)
|
||||||
|
..._audioInputs!.map((device) {
|
||||||
|
return PopupMenuItem<MediaDevice>(
|
||||||
|
value: device,
|
||||||
|
child: ListTile(
|
||||||
|
leading: (device.deviceId ==
|
||||||
|
Hardware
|
||||||
|
.instance.selectedAudioInput?.deviceId)
|
||||||
|
? const Icon(
|
||||||
|
EvaIcons.checkmarkSquare,
|
||||||
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
EvaIcons.square,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
title: Text(device.label),
|
||||||
|
),
|
||||||
|
onTap: () => _selectAudioInput(device),
|
||||||
|
);
|
||||||
|
}).toList()
|
||||||
|
];
|
||||||
|
},
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -222,11 +298,81 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
icon: const Icon(EvaIcons.micOff),
|
icon: const Icon(EvaIcons.micOff),
|
||||||
tooltip: 'un-mute audio',
|
tooltip: 'un-mute audio',
|
||||||
),
|
),
|
||||||
|
PopupMenuButton<MediaDevice>(
|
||||||
|
icon: const Icon(Icons.volume_up),
|
||||||
|
itemBuilder: (BuildContext context) {
|
||||||
|
return [
|
||||||
|
const PopupMenuItem<MediaDevice>(
|
||||||
|
value: null,
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
EvaIcons.speaker,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
title: Text('Select Audio Output'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_audioOutputs != null)
|
||||||
|
..._audioOutputs!.map((device) {
|
||||||
|
return PopupMenuItem<MediaDevice>(
|
||||||
|
value: device,
|
||||||
|
child: ListTile(
|
||||||
|
leading: (device.deviceId ==
|
||||||
|
Hardware.instance.selectedAudioOutput?.deviceId)
|
||||||
|
? const Icon(
|
||||||
|
EvaIcons.checkmarkSquare,
|
||||||
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
EvaIcons.square,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
title: Text(device.label),
|
||||||
|
),
|
||||||
|
onTap: () => _selectAudioOutput(device),
|
||||||
|
);
|
||||||
|
}).toList()
|
||||||
|
];
|
||||||
|
},
|
||||||
|
),
|
||||||
if (participant.isCameraEnabled())
|
if (participant.isCameraEnabled())
|
||||||
IconButton(
|
PopupMenuButton<MediaDevice>(
|
||||||
onPressed: _disableVideo,
|
|
||||||
icon: const Icon(EvaIcons.video),
|
icon: const Icon(EvaIcons.video),
|
||||||
tooltip: 'mute video',
|
itemBuilder: (BuildContext context) {
|
||||||
|
return [
|
||||||
|
PopupMenuItem<MediaDevice>(
|
||||||
|
value: null,
|
||||||
|
child: const ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
EvaIcons.videoOff,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
title: Text('Disable Camera'),
|
||||||
|
),
|
||||||
|
onTap: _disableVideo,
|
||||||
|
),
|
||||||
|
if (_videoInputs != null)
|
||||||
|
..._videoInputs!.map((device) {
|
||||||
|
return PopupMenuItem<MediaDevice>(
|
||||||
|
value: device,
|
||||||
|
child: ListTile(
|
||||||
|
leading:
|
||||||
|
(device.deviceId == _selectedVideoInput?.deviceId)
|
||||||
|
? const Icon(
|
||||||
|
EvaIcons.checkmarkSquare,
|
||||||
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
EvaIcons.square,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
title: Text(device.label),
|
||||||
|
),
|
||||||
|
onTap: () => _selectVideoInput(device),
|
||||||
|
);
|
||||||
|
}).toList()
|
||||||
|
];
|
||||||
|
},
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- device_info_plus_macos (0.0.1):
|
- device_info_plus_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- flutter_webrtc (0.9.2):
|
- flutter_webrtc (0.9.4):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (= 104.5112.03)
|
- WebRTC-SDK (= 104.5112.03)
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
@@ -42,9 +42,9 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7
|
device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7
|
||||||
flutter_webrtc: 335c834280a69d6db648228ab9bd0f855886f776
|
flutter_webrtc: 45cd8f5825bb32054e817708bf343ceff2c9cab8
|
||||||
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
||||||
livekit_client: 1076d27c06480d4abdda77641f6586309bc2dd3f
|
livekit_client: d229104e54e72ec7b5ffad62c14117297b5bbb23
|
||||||
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
|
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
|
||||||
shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727
|
shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727
|
||||||
WebRTC-SDK: 9f50fb5a410edc38e6fbb865fe940e3010bc8e7e
|
WebRTC-SDK: 9f50fb5a410edc38e6fbb865fe940e3010bc8e7e
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ packages:
|
|||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3"
|
version: "0.9.4"
|
||||||
google_fonts:
|
google_fonts:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'livekit_client'
|
s.name = 'livekit_client'
|
||||||
s.version = '1.1.0'
|
s.version = '1.1.1'
|
||||||
s.summary = 'Open source platform for real-time audio and video.'
|
s.summary = 'Open source platform for real-time audio and video.'
|
||||||
s.description = 'Open source platform for real-time audio and video.'
|
s.description = 'Open source platform for real-time audio and video.'
|
||||||
s.homepage = 'https://livekit.io/'
|
s.homepage = 'https://livekit.io/'
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ library livekit_client;
|
|||||||
export 'src/core/room.dart';
|
export 'src/core/room.dart';
|
||||||
export 'src/events.dart';
|
export 'src/events.dart';
|
||||||
export 'src/exceptions.dart';
|
export 'src/exceptions.dart';
|
||||||
|
export 'src/hardware/hardware.dart';
|
||||||
export 'src/livekit.dart';
|
export 'src/livekit.dart';
|
||||||
export 'src/managers/event.dart';
|
export 'src/managers/event.dart';
|
||||||
export 'src/options.dart';
|
export 'src/options.dart';
|
||||||
|
|||||||
@@ -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),
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'livekit_client'
|
s.name = 'livekit_client'
|
||||||
s.version = '1.1.0'
|
s.version = '1.1.1'
|
||||||
s.summary = 'Open source platform for real-time audio and video.'
|
s.summary = 'Open source platform for real-time audio and video.'
|
||||||
s.description = 'Open source platform for real-time audio and video.'
|
s.description = 'Open source platform for real-time audio and video.'
|
||||||
s.homepage = 'https://livekit.io/'
|
s.homepage = 'https://livekit.io/'
|
||||||
|
|||||||
+1
-1
@@ -218,7 +218,7 @@ packages:
|
|||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3"
|
version: "0.9.4"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
name: livekit_client
|
name: livekit_client
|
||||||
description: Flutter Client SDK for LiveKit.
|
description: Flutter Client SDK for LiveKit.
|
||||||
Build real-time video and audio into your apps. Supports iOS, Android, and Web.
|
Build real-time video and audio into your apps. Supports iOS, Android, and Web.
|
||||||
version: 1.1.0-hotfix
|
version: 1.1.1
|
||||||
homepage: https://livekit.io
|
homepage: https://livekit.io
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@@ -22,7 +22,7 @@ dependencies:
|
|||||||
uuid: ^3.0.6
|
uuid: ^3.0.6
|
||||||
synchronized: ^3.0.0
|
synchronized: ^3.0.0
|
||||||
protobuf: ^2.0.1
|
protobuf: ^2.0.1
|
||||||
flutter_webrtc: ^0.9.3
|
flutter_webrtc: ^0.9.4
|
||||||
dart_webrtc: ^1.0.7
|
dart_webrtc: ^1.0.7
|
||||||
device_info_plus: ^3.2.3
|
device_info_plus: ^3.2.3
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user