Capture client info (#71)

This commit is contained in:
Hiroshi Horie
2022-01-18 16:16:05 +07:00
committed by GitHub
parent 068876dc9c
commit 56aeacc291
14 changed files with 306 additions and 41 deletions
+5 -3
View File
@@ -39,12 +39,14 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
String token, {
ConnectOptions? connectOptions,
}) async {
final rtcUri = Utils.buildUri(
final rtcUri = await Utils.buildUri(
uriString,
token: token,
connectOptions: connectOptions,
);
logger.fine('SignalClient connecting with url: $rtcUri');
try {
_ws = await LiveKitWebSocket.connect(
rtcUri,
@@ -56,7 +58,7 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
);
} catch (socketError) {
// Re-build same uri for validate mode
final validateUri = Utils.buildUri(
final validateUri = await Utils.buildUri(
uriString,
token: token,
connectOptions: connectOptions,
@@ -90,7 +92,7 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
await _ws?.dispose();
_ws = null;
final rtcUri = Utils.buildUri(
final rtcUri = await Utils.buildUri(
uriString,
token: token,
reconnect: true,
+40
View File
@@ -0,0 +1,40 @@
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import '../logger.dart';
import 'native_audio.dart';
// Method channel methods to call native code.
class Native {
@internal
static const channel = MethodChannel('livekit_client');
@internal
static Future<bool> configureAudio(
NativeAudioConfiguration configuration) async {
try {
final result = await channel.invokeMethod<bool>(
'configureNativeAudio',
configuration.toMap(),
);
return result == true;
} catch (error) {
logger.warning('configureNativeAudio did throw $error');
return false;
}
}
/// Returns OS's version as a string
/// Currently only for iOS, macOS
@internal
static Future<String?> osVersionString() async {
try {
return await channel.invokeMethod<String>(
'osVersionString',
<String, dynamic>{},
);
} catch (error) {
logger.warning('appleOSVersionString did throw error: ${error}');
}
}
}
-21
View File
@@ -1,9 +1,4 @@
// https://developer.apple.com/documentation/avfaudio/avaudiosession/category
import 'package:flutter/services.dart';
import '../logger.dart';
enum AppleAudioCategory {
soloAmbient,
playback,
@@ -109,19 +104,3 @@ class NativeAudioConfiguration {
appleAudioMode: appleAudioMode ?? this.appleAudioMode,
);
}
const _lkMethodChannel = MethodChannel('livekit_client');
Future<bool> configureNativeAudio(
NativeAudioConfiguration configuration) async {
try {
final result = await _lkMethodChannel.invokeMethod<bool>(
'configureNativeAudio',
configuration.toMap(),
);
return result == true;
} catch (_) {
logger.warning('configureAudioSession did throw $_');
return false;
}
}
+2 -1
View File
@@ -8,5 +8,6 @@ PlatformType lkPlatformImplementation() {
if (Platform.isMacOS) return PlatformType.macOS;
if (Platform.isLinux) return PlatformType.linux;
if (Platform.isIOS) return PlatformType.iOS;
return PlatformType.android;
if (Platform.isAndroid) return PlatformType.android;
throw UnsupportedError('Unknown Platform');
}
+2 -1
View File
@@ -1,6 +1,7 @@
import 'package:synchronized/synchronized.dart' as sync;
import '../logger.dart';
import '../support/native.dart';
import '../support/native_audio.dart';
import '../support/platform.dart';
import 'local/audio.dart';
@@ -83,7 +84,7 @@ mixin AudioManagementMixin on AudioTrack {
logger.fine(
'[$runtimeType] configuring for ${audioTrackState} using ${config}...');
try {
await configureNativeAudio(config);
await Native.configureAudio(config);
} catch (error) {
logger.warning('[$runtimeType] Failed to configure ${error}');
}
+92 -2
View File
@@ -1,30 +1,103 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:meta/meta.dart';
import 'package:platform_detect/platform_detect.dart' as pd;
import './proto/livekit_models.pb.dart' as lk_models;
import './support/native.dart';
import 'extensions.dart';
import 'livekit.dart';
import 'logger.dart';
import 'options.dart';
import 'support/platform.dart';
import 'track/options.dart';
import 'types.dart';
extension UriExt on Uri {
@internal
bool get isSecureScheme => ['https', 'wss'].contains(scheme);
}
// Collection of state-less static methods
class Utils {
static Uri buildUri(
// DeviceInfoPlugin caches internally
static final _deviceInfoPlugin = DeviceInfoPlugin();
static Future<lk_models.ClientInfo?> _clientInfo() async {
switch (lkPlatform()) {
case PlatformType.web:
return lk_models.ClientInfo(
os: pd.operatingSystem.name.toLowerCase(),
browser: pd.browser.name.toLowerCase(),
browserVersion: pd.browser.version.canonicalizedVersion,
);
case PlatformType.windows:
return lk_models.ClientInfo(
os: 'windows',
/// [WindowsDeviceInfo] does not provide details...
);
case PlatformType.macOS:
final info = await _deviceInfoPlugin.macOsInfo;
/// [MacOsDeviceInfo.osRelease] returns Darwin version instead of macOS version
/// So call native code to get os version
String? osVersionString = await Native.osVersionString();
return lk_models.ClientInfo(
os: 'macOS',
osVersion: osVersionString,
// Confirmed
deviceModel: info.model,
);
case PlatformType.android:
final info = await _deviceInfoPlugin.androidInfo;
return lk_models.ClientInfo(
os: 'android',
osVersion: info.version.release,
deviceModel: info.model,
);
case PlatformType.iOS:
final info = await _deviceInfoPlugin.iosInfo;
String? model = info.utsname.machine;
if (model != null && ['i386', 'x86_64', 'arm64'].contains(model)) {
model = 'iOSSimulator,${model}';
}
return lk_models.ClientInfo(
os: 'iOS',
// Confirmed
osVersion: info.systemVersion,
deviceModel: model,
);
case PlatformType.linux:
final info = await _deviceInfoPlugin.linuxInfo;
return lk_models.ClientInfo(
os: 'linux',
osVersion: info.versionId,
deviceModel: info.machineId,
);
default:
// case PlatformType.fuchsia:
}
}
@internal
static Future<Uri> buildUri(
String uriString, {
required String token,
ConnectOptions? connectOptions,
bool reconnect = false,
bool validate = false,
bool forceSecure = false,
}) {
}) async {
connectOptions ??= const ConnectOptions();
final Uri uri = Uri.parse(uriString);
@@ -44,6 +117,8 @@ class Utils {
}
pathSegments.add(lastSegment);
final clientInfo = await _clientInfo();
return uri.replace(
scheme: validate ? httpScheme : wsScheme,
pathSegments: pathSegments,
@@ -54,6 +129,16 @@ class Utils {
'protocol': connectOptions.protocolVersion.toStringValue(),
'sdk': 'flutter',
'version': LiveKitClient.version,
// client info
if (clientInfo != null) ...{
if (clientInfo.hasOs()) 'os': clientInfo.os,
if (clientInfo.hasOsVersion()) 'os_version': clientInfo.osVersion,
if (clientInfo.hasDeviceModel())
'device_model': clientInfo.deviceModel,
if (clientInfo.hasBrowser()) 'browser': clientInfo.browser,
if (clientInfo.hasBrowserVersion())
'browser_version': clientInfo.browserVersion,
},
},
);
}
@@ -94,6 +179,7 @@ class Utils {
static final videoRids = ['q', 'h', 'f'];
@internal
static List<rtc.RTCRtpEncoding> encodingsFromPresets(
VideoDimensions dimensions, {
required List<VideoParameters> presets,
@@ -113,6 +199,7 @@ class Utils {
return result;
}
@internal
static List<rtc.RTCRtpEncoding>? computeVideoEncodings({
required bool isScreenShare,
VideoDimensions? dimensions,
@@ -173,6 +260,7 @@ class Utils {
);
}
@internal
static List<lk_models.VideoLayer> computeVideoLayers(
VideoDimensions dimensions,
List<rtc.RTCRtpEncoding>? encodings,
@@ -204,6 +292,7 @@ class Utils {
}).toList();
}
@internal
static lk_models.VideoQuality? videoQualityForRid(String? rid) => {
'f': lk_models.VideoQuality.HIGH,
'h': lk_models.VideoQuality.MEDIUM,
@@ -211,6 +300,7 @@ class Utils {
}[rid];
// makes a debounce func, with 1 param
@internal
static Function(T) createDebounceFunc<T>(
Function(T) f, {
Function(Function)? cancelFunc,