feat(videocall): add videocall_sdk package wrapping Juphoon jc_sdk
Full wrapper around jc_sdk v2.16.5 with clean architecture: - 7 services covering 100% of jc_sdk public API (Client, Call, Device, Channel, Push, Net, Log) - Constructor injection with GetIt DI module (follows sca_treezor pattern) - VideocallSdkManager orchestrator for init/destroy lifecycle - VideocallSdkConfig abstract for environment-specific AppKey - Stream-based callbacks for reactive UI consumption - Riverpod providers (service + stream) for feature layer - AppKey configured per environment via dart-define-from-file - Integrated in init_app.dart alongside scaTreezorModule
This commit is contained in:
@@ -2,5 +2,6 @@
|
||||
"env": "development",
|
||||
"apiBaseUrl": "https://api-neki-b2b.neki.es/gateway/api/",
|
||||
"apiOrigin": "https://neki-b2b.neki.es",
|
||||
"wsUrl": "wss://api-neki-b2b.neki.es/websocket"
|
||||
"wsUrl": "wss://api-neki-b2b.neki.es/websocket",
|
||||
"juphoonAppKey": "9efcf2d889dc8a0320925096"
|
||||
}
|
||||
@@ -2,5 +2,6 @@
|
||||
"env": "production",
|
||||
"apiBaseUrl": "https://api-platform.savefamily.app/gateway/api/",
|
||||
"apiOrigin": "https://platform.savefamily.app",
|
||||
"wsUrl": "wss://api-platform.savefamily.app/websocket"
|
||||
}
|
||||
"wsUrl": "wss://api-platform.savefamily.app/websocket",
|
||||
"juphoonAppKey": "9efcf2d889dc8a0320925096"
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"env": "staging",
|
||||
"apiBaseUrl": "https://api-platform.pre.savefamilygps.net/gateway/api/",
|
||||
"apiOrigin": "https://platform.pre.savefamilygps.net",
|
||||
"wsUrl": "wss://api-platform.pre.savefamilygps.net/websocket"
|
||||
"wsUrl": "wss://api-platform.pre.savefamilygps.net/websocket",
|
||||
"juphoonAppKey": "9efcf2d889dc8a0320925096"
|
||||
}
|
||||
@@ -3,6 +3,7 @@ abstract class Environment {
|
||||
static const apiBaseUrl = String.fromEnvironment('apiBaseUrl');
|
||||
static const apiOrigin = String.fromEnvironment('apiOrigin');
|
||||
static const wsUrl = String.fromEnvironment('wsUrl');
|
||||
static const juphoonAppKey = String.fromEnvironment('juphoonAppKey');
|
||||
|
||||
// --- Fase 2: Firebase & Sentry ---
|
||||
// static const sentryDsn = String.fromEnvironment('sentryDsn');
|
||||
|
||||
14
apps/mobile_app/lib/config/env/save_family_videocall_config.dart
vendored
Normal file
14
apps/mobile_app/lib/config/env/save_family_videocall_config.dart
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:videocall_sdk/videocall_sdk.dart';
|
||||
|
||||
import 'environment.dart';
|
||||
|
||||
class SaveFamilyVideocallConfig implements VideocallSdkConfig {
|
||||
@override
|
||||
String get appKey => Environment.juphoonAppKey;
|
||||
|
||||
@override
|
||||
String get serverAddress => '';
|
||||
|
||||
@override
|
||||
CreateParam? get createParam => null;
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:sca_treezor/sca_treezor.dart';
|
||||
import 'package:videocall_sdk/videocall_sdk.dart';
|
||||
import 'package:sf_app_platform/config/env/save_family_videocall_config.dart';
|
||||
import 'package:sf_app_platform/config/env/environment_enum.dart';
|
||||
import 'package:sf_app_platform/config/env/save_family_env_config.dart';
|
||||
import 'package:sf_app_platform/core/config/app_mode.dart';
|
||||
@@ -24,6 +26,7 @@ Future<void> initApp(EnvironmentEnum env) async {
|
||||
|
||||
navigationModule();
|
||||
scaTreezorModule();
|
||||
videocallSdkModule(SaveFamilyVideocallConfig());
|
||||
themePackages();
|
||||
|
||||
await setupFirebase(env);
|
||||
|
||||
@@ -91,6 +91,8 @@ dependencies:
|
||||
path: ../../packages/sca_treezor
|
||||
payments:
|
||||
path: ../../packages/payments
|
||||
videocall_sdk:
|
||||
path: ../../packages/videocall_sdk
|
||||
#dependencies go here
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_svg: ^2.2.2
|
||||
|
||||
31
packages/videocall_sdk/.gitignore
vendored
Normal file
31
packages/videocall_sdk/.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
/build/
|
||||
/coverage/
|
||||
3
packages/videocall_sdk/CHANGELOG.md
Normal file
3
packages/videocall_sdk/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.0.1
|
||||
|
||||
* Initial release. Wrapper around Juphoon jc_sdk for video calling.
|
||||
1
packages/videocall_sdk/LICENSE
Normal file
1
packages/videocall_sdk/LICENSE
Normal file
@@ -0,0 +1 @@
|
||||
TODO: Add your license here.
|
||||
5
packages/videocall_sdk/README.md
Normal file
5
packages/videocall_sdk/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## videocall_sdk
|
||||
|
||||
Wrapper around Juphoon jc_sdk for video calling in SaveFamily.
|
||||
|
||||
Provides a clean Dart API over the native Juphoon SDK, isolating the dependency from the rest of the app.
|
||||
4
packages/videocall_sdk/analysis_options.yaml
Normal file
4
packages/videocall_sdk/analysis_options.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:jc_sdk/jc_sdk.dart';
|
||||
|
||||
abstract class VideocallSdkConfig {
|
||||
String get appKey;
|
||||
String get serverAddress;
|
||||
CreateParam? get createParam;
|
||||
}
|
||||
65
packages/videocall_sdk/lib/src/di/videocall_sdk_module.dart
Normal file
65
packages/videocall_sdk/lib/src/di/videocall_sdk_module.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../config/videocall_sdk_config.dart';
|
||||
import '../manager/videocall_sdk_manager.dart';
|
||||
import '../services/videocall_call_service.dart';
|
||||
import '../services/videocall_channel_service.dart';
|
||||
import '../services/videocall_client.dart';
|
||||
import '../services/videocall_device_service.dart';
|
||||
import '../services/videocall_net_service.dart';
|
||||
import '../services/videocall_push_service.dart';
|
||||
|
||||
void videocallSdkModule(VideocallSdkConfig config) {
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
// Config
|
||||
getIt.registerSingleton<VideocallSdkConfig>(config);
|
||||
|
||||
// Core client
|
||||
getIt.registerLazySingleton<VideocallClient>(
|
||||
() => VideocallClient(getIt<VideocallSdkConfig>()),
|
||||
);
|
||||
|
||||
// Device (depends on Client)
|
||||
getIt.registerLazySingleton<VideocallDeviceService>(
|
||||
() => VideocallDeviceService(client: getIt<VideocallClient>()),
|
||||
);
|
||||
|
||||
// Call (depends on Client + Device)
|
||||
getIt.registerLazySingleton<VideocallCallService>(
|
||||
() => VideocallCallService(
|
||||
client: getIt<VideocallClient>(),
|
||||
deviceService: getIt<VideocallDeviceService>(),
|
||||
),
|
||||
);
|
||||
|
||||
// Channel (depends on Client + Device)
|
||||
getIt.registerLazySingleton<VideocallChannelService>(
|
||||
() => VideocallChannelService(
|
||||
client: getIt<VideocallClient>(),
|
||||
deviceService: getIt<VideocallDeviceService>(),
|
||||
),
|
||||
);
|
||||
|
||||
// Push (depends on Client)
|
||||
getIt.registerLazySingleton<VideocallPushService>(
|
||||
() => VideocallPushService(client: getIt<VideocallClient>()),
|
||||
);
|
||||
|
||||
// Net (standalone)
|
||||
getIt.registerLazySingleton<VideocallNetService>(
|
||||
() => VideocallNetService(),
|
||||
);
|
||||
|
||||
// Manager (orchestrator)
|
||||
getIt.registerLazySingleton<VideocallSdkManager>(
|
||||
() => VideocallSdkManager(
|
||||
client: getIt<VideocallClient>(),
|
||||
deviceService: getIt<VideocallDeviceService>(),
|
||||
callService: getIt<VideocallCallService>(),
|
||||
channelService: getIt<VideocallChannelService>(),
|
||||
pushService: getIt<VideocallPushService>(),
|
||||
netService: getIt<VideocallNetService>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import '../services/videocall_call_service.dart';
|
||||
import '../services/videocall_channel_service.dart';
|
||||
import '../services/videocall_client.dart';
|
||||
import '../services/videocall_device_service.dart';
|
||||
import '../services/videocall_net_service.dart';
|
||||
import '../services/videocall_push_service.dart';
|
||||
|
||||
class VideocallSdkManager {
|
||||
VideocallSdkManager({
|
||||
required this.client,
|
||||
required this.deviceService,
|
||||
required this.callService,
|
||||
required this.channelService,
|
||||
required this.pushService,
|
||||
required this.netService,
|
||||
});
|
||||
|
||||
final VideocallClient client;
|
||||
final VideocallDeviceService deviceService;
|
||||
final VideocallCallService callService;
|
||||
final VideocallChannelService channelService;
|
||||
final VideocallPushService pushService;
|
||||
final VideocallNetService netService;
|
||||
|
||||
bool _initialized = false;
|
||||
bool get isInitialized => _initialized;
|
||||
|
||||
Future<bool> initialize() async {
|
||||
if (_initialized) return true;
|
||||
|
||||
// Phase 1: Client (no deps)
|
||||
final clientOk = await client.initialize();
|
||||
if (!clientOk) return false;
|
||||
|
||||
// Phase 2: Device + Net (depend on Client only)
|
||||
final deviceOk = await deviceService.initialize();
|
||||
if (!deviceOk) return false;
|
||||
netService.initialize();
|
||||
|
||||
// Phase 3: Call + Channel + Push (depend on Client + Device)
|
||||
final phase3 = await Future.wait([
|
||||
callService.initialize(),
|
||||
channelService.initialize(),
|
||||
pushService.initialize(),
|
||||
]);
|
||||
if (!phase3.every((r) => r)) return false;
|
||||
|
||||
_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> destroy() async {
|
||||
if (!_initialized) return;
|
||||
|
||||
// Reverse order
|
||||
await pushService.destroy();
|
||||
await channelService.destroy();
|
||||
await callService.destroy();
|
||||
netService.uninitialize();
|
||||
await deviceService.destroy();
|
||||
await client.destroy();
|
||||
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
callService.dispose();
|
||||
channelService.dispose();
|
||||
pushService.dispose();
|
||||
netService.dispose();
|
||||
deviceService.dispose();
|
||||
client.dispose();
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
enum CallDirection {
|
||||
incoming,
|
||||
outgoing,
|
||||
}
|
||||
9
packages/videocall_sdk/lib/src/models/call_state.dart
Normal file
9
packages/videocall_sdk/lib/src/models/call_state.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
enum VideocallState {
|
||||
idle,
|
||||
pending,
|
||||
connecting,
|
||||
talking,
|
||||
ok,
|
||||
canceled,
|
||||
missed,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
enum LoginFailureReason {
|
||||
auth,
|
||||
network,
|
||||
appkey,
|
||||
noUser,
|
||||
timeout,
|
||||
serverLogout,
|
||||
anotherDeviceLoggedIn,
|
||||
tokenMismatch,
|
||||
tokenExpired,
|
||||
unknown,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
enum VideocallClientState {
|
||||
notInitialized,
|
||||
idle,
|
||||
loggingIn,
|
||||
loggedIn,
|
||||
loggingOut,
|
||||
}
|
||||
30
packages/videocall_sdk/lib/src/models/videocall_item.dart
Normal file
30
packages/videocall_sdk/lib/src/models/videocall_item.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'call_direction.dart';
|
||||
import 'call_state.dart';
|
||||
|
||||
class VideocallItem {
|
||||
const VideocallItem({
|
||||
required this.userId,
|
||||
required this.isVideo,
|
||||
required this.direction,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
final String userId;
|
||||
final bool isVideo;
|
||||
final CallDirection direction;
|
||||
final VideocallState state;
|
||||
|
||||
VideocallItem copyWith({
|
||||
String? userId,
|
||||
bool? isVideo,
|
||||
CallDirection? direction,
|
||||
VideocallState? state,
|
||||
}) {
|
||||
return VideocallItem(
|
||||
userId: userId ?? this.userId,
|
||||
isVideo: isVideo ?? this.isVideo,
|
||||
direction: direction ?? this.direction,
|
||||
state: state ?? this.state,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../manager/videocall_sdk_manager.dart';
|
||||
import '../models/videocall_client_state.dart';
|
||||
import '../models/videocall_item.dart';
|
||||
import '../services/videocall_call_service.dart';
|
||||
import '../services/videocall_channel_service.dart';
|
||||
import '../services/videocall_client.dart';
|
||||
import '../services/videocall_device_service.dart';
|
||||
import '../services/videocall_net_service.dart';
|
||||
import '../services/videocall_push_service.dart';
|
||||
|
||||
// -- Service providers (thin wrappers over GetIt) --
|
||||
|
||||
final videocallManagerProvider = Provider<VideocallSdkManager>((ref) {
|
||||
return GetIt.I<VideocallSdkManager>();
|
||||
});
|
||||
|
||||
final videocallClientProvider = Provider<VideocallClient>((ref) {
|
||||
return GetIt.I<VideocallClient>();
|
||||
});
|
||||
|
||||
final videocallCallServiceProvider = Provider<VideocallCallService>((ref) {
|
||||
return GetIt.I<VideocallCallService>();
|
||||
});
|
||||
|
||||
final videocallDeviceServiceProvider = Provider<VideocallDeviceService>((ref) {
|
||||
return GetIt.I<VideocallDeviceService>();
|
||||
});
|
||||
|
||||
final videocallChannelServiceProvider =
|
||||
Provider<VideocallChannelService>((ref) {
|
||||
return GetIt.I<VideocallChannelService>();
|
||||
});
|
||||
|
||||
final videocallPushServiceProvider = Provider<VideocallPushService>((ref) {
|
||||
return GetIt.I<VideocallPushService>();
|
||||
});
|
||||
|
||||
final videocallNetServiceProvider = Provider<VideocallNetService>((ref) {
|
||||
return GetIt.I<VideocallNetService>();
|
||||
});
|
||||
|
||||
// -- Stream providers (for reactive UI consumption) --
|
||||
|
||||
final videocallClientStateProvider =
|
||||
StreamProvider<VideocallClientState>((ref) {
|
||||
return ref.watch(videocallClientProvider).stateStream;
|
||||
});
|
||||
|
||||
final videocallCurrentCallProvider = StreamProvider<VideocallItem?>((ref) {
|
||||
final callService = ref.watch(videocallCallServiceProvider);
|
||||
|
||||
Stream<VideocallItem?> stream() async* {
|
||||
yield callService.currentItem;
|
||||
yield* callService.callItemUpdateStream
|
||||
.map((_) => callService.currentItem);
|
||||
}
|
||||
|
||||
return stream();
|
||||
});
|
||||
@@ -0,0 +1,313 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:jc_sdk/jc_sdk.dart';
|
||||
|
||||
import '../models/call_direction.dart';
|
||||
import '../models/call_state.dart';
|
||||
import '../models/videocall_item.dart';
|
||||
import 'videocall_client.dart';
|
||||
import 'videocall_device_service.dart';
|
||||
|
||||
class VideocallCallService with JCCallCallback {
|
||||
VideocallCallService({
|
||||
required VideocallClient client,
|
||||
required VideocallDeviceService deviceService,
|
||||
}) : _clientRef = client,
|
||||
_deviceRef = deviceService;
|
||||
|
||||
final VideocallClient _clientRef;
|
||||
final VideocallDeviceService _deviceRef;
|
||||
JCCall? _call;
|
||||
JCCall? get call => _call;
|
||||
|
||||
// -- Streams --
|
||||
|
||||
final _callItemAddController = StreamController<VideocallItem>.broadcast();
|
||||
Stream<VideocallItem> get callItemAddStream => _callItemAddController.stream;
|
||||
|
||||
final _callItemUpdateController = StreamController<VideocallItem>.broadcast();
|
||||
Stream<VideocallItem> get callItemUpdateStream =>
|
||||
_callItemUpdateController.stream;
|
||||
|
||||
final _callItemRemoveController =
|
||||
StreamController<({int reason, String description})>.broadcast();
|
||||
Stream<({int reason, String description})> get callItemRemoveStream =>
|
||||
_callItemRemoveController.stream;
|
||||
|
||||
final _missedCallController = StreamController<VideocallItem>.broadcast();
|
||||
Stream<VideocallItem> get missedCallStream => _missedCallController.stream;
|
||||
|
||||
final _messageReceivedController =
|
||||
StreamController<({String type, String content, JCCallItem item})>
|
||||
.broadcast();
|
||||
Stream<({String type, String content, JCCallItem item})>
|
||||
get messageReceivedStream => _messageReceivedController.stream;
|
||||
|
||||
final _dtmfReceivedController =
|
||||
StreamController<({JCCallItem item, int value})>.broadcast();
|
||||
Stream<({JCCallItem item, int value})> get dtmfReceivedStream =>
|
||||
_dtmfReceivedController.stream;
|
||||
|
||||
final _earlyMediaController = StreamController<JCCallItem>.broadcast();
|
||||
Stream<JCCallItem> get earlyMediaStream => _earlyMediaController.stream;
|
||||
|
||||
VideocallItem? _currentItem;
|
||||
VideocallItem? get currentItem => _currentItem;
|
||||
|
||||
// -- Lifecycle --
|
||||
|
||||
Future<bool> initialize() async {
|
||||
final client = _clientRef.client;
|
||||
final mediaDevice = _deviceRef.mediaDevice;
|
||||
if (client == null || mediaDevice == null) return false;
|
||||
_call = await JCCall.create(client, mediaDevice, this);
|
||||
return _call != null;
|
||||
}
|
||||
|
||||
Future<bool> destroy() async => JCCall.destroy();
|
||||
|
||||
// -- Call actions --
|
||||
|
||||
Future<bool> startCall({
|
||||
required String userId,
|
||||
required bool isVideo,
|
||||
CallParam? callParam,
|
||||
}) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.call(userId, isVideo, callParam);
|
||||
}
|
||||
|
||||
Future<bool> answerCall({required bool isVideo}) async {
|
||||
final item = await _call?.getActiveCallItem();
|
||||
if (item == null) return false;
|
||||
return _call!.answer(item, isVideo);
|
||||
}
|
||||
|
||||
Future<bool> hangUp({int? reason, String? description}) async {
|
||||
final item = await _call?.getActiveCallItem();
|
||||
if (item == null) return false;
|
||||
return _call!.term(item, reason ?? JCCall.REASON_NONE, description ?? '');
|
||||
}
|
||||
|
||||
Future<bool> termCall(
|
||||
JCCallItem item, int reason, String description) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.term(item, reason, description);
|
||||
}
|
||||
|
||||
// -- Mute/Audio --
|
||||
|
||||
Future<bool> mute(JCCallItem item) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.mute(item);
|
||||
}
|
||||
|
||||
Future<bool> muteMicrophone(JCCallItem item, bool mute) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.muteMicrophone(item, mute);
|
||||
}
|
||||
|
||||
Future<bool> muteSpeaker(JCCallItem item, bool mute) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.muteSpeaker(item, mute);
|
||||
}
|
||||
|
||||
Future<bool> setMicScale(JCCallItem item, int scale) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.setMicScale(item, scale);
|
||||
}
|
||||
|
||||
// -- Hold --
|
||||
|
||||
Future<bool> hold(JCCallItem item) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.hold(item);
|
||||
}
|
||||
|
||||
Future<bool> becomeActive(JCCallItem item) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.becomeActive(item);
|
||||
}
|
||||
|
||||
// -- Video --
|
||||
|
||||
Future<bool> enableUploadVideoStream(JCCallItem item) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.enableUploadVideoStream(item);
|
||||
}
|
||||
|
||||
Future<JCMediaDeviceVideoCanvas?> startLocalVideo(
|
||||
{int renderType = JCMediaDevice.RENDER_FULL_AUTO}) async {
|
||||
final item = await _call?.getActiveCallItem();
|
||||
if (item == null) return null;
|
||||
return item.startSelfVideo(renderType);
|
||||
}
|
||||
|
||||
Future<bool> stopLocalVideo() async {
|
||||
final item = await _call?.getActiveCallItem();
|
||||
if (item == null) return false;
|
||||
return item.stopSelfVideo();
|
||||
}
|
||||
|
||||
Future<JCMediaDeviceVideoCanvas?> startRemoteVideo(
|
||||
{int renderType = JCMediaDevice.RENDER_FULL_CONTENT}) async {
|
||||
final item = await _call?.getActiveCallItem();
|
||||
if (item == null) return null;
|
||||
return item.startOtherVideo(renderType);
|
||||
}
|
||||
|
||||
Future<bool> stopRemoteVideo() async {
|
||||
final item = await _call?.getActiveCallItem();
|
||||
if (item == null) return false;
|
||||
return item.stopOtherVideo();
|
||||
}
|
||||
|
||||
// -- Recording --
|
||||
|
||||
Future<bool> audioRecord(
|
||||
JCCallItem item, bool enable, String filePath) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.audioRecord(item, enable, filePath);
|
||||
}
|
||||
|
||||
Future<bool> videoRecord(
|
||||
JCCallItem item, {
|
||||
required bool enable,
|
||||
required bool remote,
|
||||
required int width,
|
||||
required int height,
|
||||
required String filePath,
|
||||
required bool bothAudio,
|
||||
required int keyframe,
|
||||
}) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.videoRecord(
|
||||
item, enable, remote, width, height, filePath, bothAudio, keyframe);
|
||||
}
|
||||
|
||||
// -- Messaging --
|
||||
|
||||
Future<bool> sendMessage(
|
||||
JCCallItem item, String type, String content) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.sendMessage(item, type, content);
|
||||
}
|
||||
|
||||
Future<bool> sendDtmf(JCCallItem item, int value) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.sendDtmf(item, value);
|
||||
}
|
||||
|
||||
// -- Items --
|
||||
|
||||
Future<List<JCCallItem>?> getCallItems() async => _call?.getCallItems();
|
||||
|
||||
Future<JCCallItem?> getActiveCallItem() async =>
|
||||
_call?.getActiveCallItem();
|
||||
|
||||
// -- Config --
|
||||
|
||||
Future<String> getStatistics() async {
|
||||
if (_call == null) return '';
|
||||
return _call!.getStatistics();
|
||||
}
|
||||
|
||||
Future<bool> updateMediaConfig(MediaConfig mediaConfig) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.updateMediaConfig(mediaConfig);
|
||||
}
|
||||
|
||||
Future<MediaConfig?> getMediaConfig() async => _call?.getMediaConfig();
|
||||
|
||||
Future<bool> setMaxCallNum(int num) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.setMaxCallNum(num);
|
||||
}
|
||||
|
||||
Future<bool> setTermWhenNetDisconnected(bool term) async {
|
||||
if (_call == null) return false;
|
||||
return _call!.setTermWhenNetDisconnected(term);
|
||||
}
|
||||
|
||||
static Future<MediaConfig> generateMediaConfigByMode(int mode) =>
|
||||
JCCall.generateByMode(mode);
|
||||
|
||||
// -- Dispose --
|
||||
|
||||
void dispose() {
|
||||
_callItemAddController.close();
|
||||
_callItemUpdateController.close();
|
||||
_callItemRemoveController.close();
|
||||
_missedCallController.close();
|
||||
_messageReceivedController.close();
|
||||
_dtmfReceivedController.close();
|
||||
_earlyMediaController.close();
|
||||
}
|
||||
|
||||
// -- Internal --
|
||||
|
||||
VideocallState _mapCallState(int state) {
|
||||
if (state == JCCall.STATE_PENDING) return VideocallState.pending;
|
||||
if (state == JCCall.STATE_CONNECTING) return VideocallState.connecting;
|
||||
if (state == JCCall.STATE_TALKING) return VideocallState.talking;
|
||||
if (state == JCCall.STATE_OK) return VideocallState.ok;
|
||||
if (state == JCCall.STATE_CANCEL) return VideocallState.canceled;
|
||||
if (state == JCCall.STATE_CANCELED) return VideocallState.canceled;
|
||||
if (state == JCCall.STATE_MISSED) return VideocallState.missed;
|
||||
return VideocallState.idle;
|
||||
}
|
||||
|
||||
VideocallItem _buildItem(JCCallItem item, VideocallState state) {
|
||||
return VideocallItem(
|
||||
userId: item.getUserId(),
|
||||
isVideo: item.getVideo(),
|
||||
direction: item.getDirection() == JCCall.DIRECTION_IN
|
||||
? CallDirection.incoming
|
||||
: CallDirection.outgoing,
|
||||
state: state,
|
||||
);
|
||||
}
|
||||
|
||||
// -- JCCallCallback --
|
||||
|
||||
@override
|
||||
void onCallItemAdd(JCCallItem item) {
|
||||
_currentItem = _buildItem(item, VideocallState.pending);
|
||||
_callItemAddController.add(_currentItem!);
|
||||
}
|
||||
|
||||
@override
|
||||
void onCallItemUpdate(JCCallItem item, ChangeParam changeParam) {
|
||||
_currentItem = _buildItem(item, _mapCallState(item.getState()));
|
||||
_callItemUpdateController.add(_currentItem!);
|
||||
}
|
||||
|
||||
@override
|
||||
void onCallItemRemove(JCCallItem item, int reason, String description) {
|
||||
_currentItem = null;
|
||||
_callItemRemoveController.add((reason: reason, description: description));
|
||||
}
|
||||
|
||||
@override
|
||||
void onMessageReceive(String type, String content, JCCallItem item) {
|
||||
_messageReceivedController.add((type: type, content: content, item: item));
|
||||
}
|
||||
|
||||
@override
|
||||
void onMissedCallItem(JCCallItem item) {
|
||||
_missedCallController.add(_buildItem(item, VideocallState.missed));
|
||||
}
|
||||
|
||||
@override
|
||||
void onDtmfReceived(JCCallItem item, int value) {
|
||||
_dtmfReceivedController.add((item: item, value: value));
|
||||
}
|
||||
|
||||
@override
|
||||
void onEarlyMediaReceived(JCCallItem item) {
|
||||
_earlyMediaController.add(item);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSipRingInfoReceived(JCCallItem item, String callSipType) {}
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:jc_sdk/jc_sdk.dart';
|
||||
|
||||
import 'videocall_client.dart';
|
||||
import 'videocall_device_service.dart';
|
||||
|
||||
class VideocallChannelService with JCMediaChannelCallback {
|
||||
VideocallChannelService({
|
||||
required VideocallClient client,
|
||||
required VideocallDeviceService deviceService,
|
||||
}) : _clientRef = client,
|
||||
_deviceRef = deviceService;
|
||||
|
||||
final VideocallClient _clientRef;
|
||||
final VideocallDeviceService _deviceRef;
|
||||
JCMediaChannel? _channel;
|
||||
JCMediaChannel? get channel => _channel;
|
||||
|
||||
// -- Streams --
|
||||
|
||||
final _stateChangeController =
|
||||
StreamController<({int state, int oldState})>.broadcast();
|
||||
Stream<({int state, int oldState})> get stateChangeStream =>
|
||||
_stateChangeController.stream;
|
||||
|
||||
final _joinController =
|
||||
StreamController<({bool result, int reason, String channelId})>
|
||||
.broadcast();
|
||||
Stream<({bool result, int reason, String channelId})> get joinStream =>
|
||||
_joinController.stream;
|
||||
|
||||
final _leaveController =
|
||||
StreamController<({int reason, String channelId})>.broadcast();
|
||||
Stream<({int reason, String channelId})> get leaveStream =>
|
||||
_leaveController.stream;
|
||||
|
||||
final _stopController =
|
||||
StreamController<({bool result, int reason})>.broadcast();
|
||||
Stream<({bool result, int reason})> get stopStream => _stopController.stream;
|
||||
|
||||
final _participantJoinController =
|
||||
StreamController<JCMediaChannelParticipant>.broadcast();
|
||||
Stream<JCMediaChannelParticipant> get participantJoinStream =>
|
||||
_participantJoinController.stream;
|
||||
|
||||
final _participantLeftController =
|
||||
StreamController<JCMediaChannelParticipant>.broadcast();
|
||||
Stream<JCMediaChannelParticipant> get participantLeftStream =>
|
||||
_participantLeftController.stream;
|
||||
|
||||
final _participantUpdateController = StreamController<
|
||||
({
|
||||
JCMediaChannelParticipant participant,
|
||||
ChannelChangeParam changeParam,
|
||||
})>.broadcast();
|
||||
Stream<
|
||||
({
|
||||
JCMediaChannelParticipant participant,
|
||||
ChannelChangeParam changeParam,
|
||||
})> get participantUpdateStream => _participantUpdateController.stream;
|
||||
|
||||
final _participantVolumeChangeController =
|
||||
StreamController<JCMediaChannelParticipant>.broadcast();
|
||||
Stream<JCMediaChannelParticipant> get participantVolumeChangeStream =>
|
||||
_participantVolumeChangeController.stream;
|
||||
|
||||
final _queryController = StreamController<
|
||||
({
|
||||
int operationId,
|
||||
bool result,
|
||||
int reason,
|
||||
JCMediaChannelQueryInfo queryInfo,
|
||||
})>.broadcast();
|
||||
Stream<
|
||||
({
|
||||
int operationId,
|
||||
bool result,
|
||||
int reason,
|
||||
JCMediaChannelQueryInfo queryInfo,
|
||||
})> get queryStream => _queryController.stream;
|
||||
|
||||
final _propertyChangeController =
|
||||
StreamController<PropChangeParam>.broadcast();
|
||||
Stream<PropChangeParam> get propertyChangeStream =>
|
||||
_propertyChangeController.stream;
|
||||
|
||||
final _messageReceivedController =
|
||||
StreamController<({String type, String content, String fromUserId})>
|
||||
.broadcast();
|
||||
Stream<({String type, String content, String fromUserId})>
|
||||
get messageReceivedStream => _messageReceivedController.stream;
|
||||
|
||||
final _inviteSipUserResultController =
|
||||
StreamController<({int operationId, bool result, int reason})>
|
||||
.broadcast();
|
||||
Stream<({int operationId, bool result, int reason})>
|
||||
get inviteSipUserResultStream => _inviteSipUserResultController.stream;
|
||||
|
||||
// -- Lifecycle --
|
||||
|
||||
Future<bool> initialize() async {
|
||||
final client = _clientRef.client;
|
||||
final mediaDevice = _deviceRef.mediaDevice;
|
||||
if (client == null || mediaDevice == null) return false;
|
||||
_channel = await JCMediaChannel.create(client, mediaDevice, this);
|
||||
return _channel != null;
|
||||
}
|
||||
|
||||
Future<bool> destroy() async => JCMediaChannel.destroy();
|
||||
|
||||
// -- Channel actions --
|
||||
|
||||
Future<bool> join(String channelId, {JoinParam? joinParam}) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.join(channelId, joinParam);
|
||||
}
|
||||
|
||||
Future<bool> leave() async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.leave();
|
||||
}
|
||||
|
||||
Future<bool> stop() async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.stop();
|
||||
}
|
||||
|
||||
Future<int> query(String channelId) async {
|
||||
if (_channel == null) return -1;
|
||||
return _channel!.query(channelId);
|
||||
}
|
||||
|
||||
// -- Audio/Video streams --
|
||||
|
||||
Future<bool> enableUploadAudioStream(bool enable) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.enableUploadAudioStream(enable);
|
||||
}
|
||||
|
||||
Future<bool> enableUploadVideoStream(bool enable) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.enableUploadVideoStream(enable);
|
||||
}
|
||||
|
||||
Future<bool> enableAudioOutput(bool enable) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.enableAudioOutput(enable);
|
||||
}
|
||||
|
||||
Future<bool> requestVideo(
|
||||
JCMediaChannelParticipant participant, int pictureSize) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.requestVideo(participant, pictureSize);
|
||||
}
|
||||
|
||||
Future<bool> requestScreenVideo(String screenUri, int pictureSize) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.requestScreenVideo(screenUri, pictureSize);
|
||||
}
|
||||
|
||||
// -- Screen share / CDN / Recording --
|
||||
|
||||
Future<bool> enableScreenShare(
|
||||
bool enable, ScreenShareParam? screenShareParam) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.enableScreenShare(enable, screenShareParam);
|
||||
}
|
||||
|
||||
Future<bool> enableCdn(bool enable, int keyInterval) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.enableCdn(enable, keyInterval);
|
||||
}
|
||||
|
||||
Future<bool> enableRecord(bool enable, {RecordParam? recordParam}) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.enableRecord(enable, recordParam);
|
||||
}
|
||||
|
||||
// -- Participants --
|
||||
|
||||
Future<List<JCMediaChannelParticipant>?> getParticipants() async =>
|
||||
_channel?.getParticipants();
|
||||
|
||||
Future<JCMediaChannelParticipant?> getParticipant(String userId) async =>
|
||||
_channel?.getParticipant(userId);
|
||||
|
||||
Future<bool> kick(JCMediaChannelParticipant participant) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.kick(participant);
|
||||
}
|
||||
|
||||
Future<int> inviteSipUser(String userId, {SipParam? sipParam}) async {
|
||||
if (_channel == null) return -1;
|
||||
return _channel!.inviteSipUser(userId, sipParam);
|
||||
}
|
||||
|
||||
// -- Custom roles/state --
|
||||
|
||||
Future<bool> setCustomRole(int customRole,
|
||||
{JCMediaChannelParticipant? participant}) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.setCustomRole(customRole, participant);
|
||||
}
|
||||
|
||||
Future<int> getCustomRole() async {
|
||||
if (_channel == null) return 0;
|
||||
return _channel!.getCustomRole();
|
||||
}
|
||||
|
||||
Future<bool> setCustomState(int customState,
|
||||
{JCMediaChannelParticipant? participant}) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.setCustomState(customState, participant);
|
||||
}
|
||||
|
||||
Future<int> getCustomState() async {
|
||||
if (_channel == null) return 0;
|
||||
return _channel!.getCustomState();
|
||||
}
|
||||
|
||||
// -- Channel properties --
|
||||
|
||||
Future<int> setCustomProperty(String property) async {
|
||||
if (_channel == null) return -1;
|
||||
return _channel!.setCustomProperty(property);
|
||||
}
|
||||
|
||||
Future<String> getCustomProperty() async {
|
||||
if (_channel == null) return '';
|
||||
return _channel!.getCustomProperty();
|
||||
}
|
||||
|
||||
// -- Channel info --
|
||||
|
||||
Future<String> getChannelUri() async {
|
||||
if (_channel == null) return '';
|
||||
return _channel!.getChannelUri();
|
||||
}
|
||||
|
||||
Future<String> getChannelId() async {
|
||||
if (_channel == null) return '';
|
||||
return _channel!.getChannelId();
|
||||
}
|
||||
|
||||
Future<String> getSessionId() async {
|
||||
if (_channel == null) return '';
|
||||
return _channel!.getSessionId();
|
||||
}
|
||||
|
||||
Future<int> getConfId() async {
|
||||
if (_channel == null) return -1;
|
||||
return _channel!.getConfId();
|
||||
}
|
||||
|
||||
Future<String> getPassword() async {
|
||||
if (_channel == null) return '';
|
||||
return _channel!.getPassword();
|
||||
}
|
||||
|
||||
Future<int> getChannelNumber() async {
|
||||
if (_channel == null) return 0;
|
||||
return _channel!.getChannelNumber();
|
||||
}
|
||||
|
||||
Future<String> getTitle() async {
|
||||
if (_channel == null) return '';
|
||||
return _channel!.getTitle();
|
||||
}
|
||||
|
||||
Future<int> getChannelState() async {
|
||||
if (_channel == null) return JCMediaChannel.STATE_IDLE;
|
||||
return _channel!.getState();
|
||||
}
|
||||
|
||||
Future<bool> getUploadLocalAudio() async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.getUploadLocalAudio();
|
||||
}
|
||||
|
||||
Future<bool> getUploadLocalVideo() async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.getUploadLocalVideo();
|
||||
}
|
||||
|
||||
Future<bool> getAudioOutput() async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.getAudioOutput();
|
||||
}
|
||||
|
||||
Future<int> getRecordState() async {
|
||||
if (_channel == null) return JCMediaChannel.RECORD_STATE_NONE;
|
||||
return _channel!.getRecordState();
|
||||
}
|
||||
|
||||
Future<int> getCdnState() async {
|
||||
if (_channel == null) return JCMediaChannel.CDN_STATE_NONE;
|
||||
return _channel!.getCdnState();
|
||||
}
|
||||
|
||||
// -- Messaging --
|
||||
|
||||
Future<bool> sendMessage(
|
||||
String type, String content, String toUserId) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.sendMessage(type, content, toUserId);
|
||||
}
|
||||
|
||||
Future<bool> sendCommand(String name, String param) async {
|
||||
if (_channel == null) return false;
|
||||
return _channel!.sendCommand(name, param);
|
||||
}
|
||||
|
||||
// -- Statistics --
|
||||
|
||||
Future<String> getStatistics() async {
|
||||
if (_channel == null) return '';
|
||||
return _channel!.getStatistics();
|
||||
}
|
||||
|
||||
// -- Dispose --
|
||||
|
||||
void dispose() {
|
||||
_stateChangeController.close();
|
||||
_joinController.close();
|
||||
_leaveController.close();
|
||||
_stopController.close();
|
||||
_participantJoinController.close();
|
||||
_participantLeftController.close();
|
||||
_participantUpdateController.close();
|
||||
_participantVolumeChangeController.close();
|
||||
_queryController.close();
|
||||
_propertyChangeController.close();
|
||||
_messageReceivedController.close();
|
||||
_inviteSipUserResultController.close();
|
||||
}
|
||||
|
||||
// -- JCMediaChannelCallback --
|
||||
|
||||
@override
|
||||
void onMediaChannelStateChange(int state, int oldState) =>
|
||||
_stateChangeController.add((state: state, oldState: oldState));
|
||||
|
||||
@override
|
||||
void onMediaChannelPropertyChange(PropChangeParam propChangeParam) =>
|
||||
_propertyChangeController.add(propChangeParam);
|
||||
|
||||
@override
|
||||
void onJoin(bool result, int reason, String channelId) =>
|
||||
_joinController
|
||||
.add((result: result, reason: reason, channelId: channelId));
|
||||
|
||||
@override
|
||||
void onLeave(int reason, String channelId) =>
|
||||
_leaveController.add((reason: reason, channelId: channelId));
|
||||
|
||||
@override
|
||||
void onStop(bool result, int reason) =>
|
||||
_stopController.add((result: result, reason: reason));
|
||||
|
||||
@override
|
||||
void onQuery(int operationId, bool result, int reason,
|
||||
JCMediaChannelQueryInfo queryInfo) =>
|
||||
_queryController.add((
|
||||
operationId: operationId,
|
||||
result: result,
|
||||
reason: reason,
|
||||
queryInfo: queryInfo,
|
||||
));
|
||||
|
||||
@override
|
||||
void onParticipantJoin(JCMediaChannelParticipant participant) =>
|
||||
_participantJoinController.add(participant);
|
||||
|
||||
@override
|
||||
void onParticipantLeft(JCMediaChannelParticipant participant) =>
|
||||
_participantLeftController.add(participant);
|
||||
|
||||
@override
|
||||
void onParticipantUpdate(JCMediaChannelParticipant participant,
|
||||
ChannelChangeParam changeParam) =>
|
||||
_participantUpdateController
|
||||
.add((participant: participant, changeParam: changeParam));
|
||||
|
||||
@override
|
||||
void onParticipantVolumeChange(JCMediaChannelParticipant participant) =>
|
||||
_participantVolumeChangeController.add(participant);
|
||||
|
||||
@override
|
||||
void onChannelMessageReceive(
|
||||
String type, String content, String fromUserId) =>
|
||||
_messageReceivedController
|
||||
.add((type: type, content: content, fromUserId: fromUserId));
|
||||
|
||||
@override
|
||||
void onInviteSipUserResult(int operationId, bool result, int reason) =>
|
||||
_inviteSipUserResultController
|
||||
.add((operationId: operationId, result: result, reason: reason));
|
||||
}
|
||||
247
packages/videocall_sdk/lib/src/services/videocall_client.dart
Normal file
247
packages/videocall_sdk/lib/src/services/videocall_client.dart
Normal file
@@ -0,0 +1,247 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:jc_sdk/jc_sdk.dart';
|
||||
|
||||
import '../config/videocall_sdk_config.dart';
|
||||
import '../models/login_failure_reason.dart';
|
||||
import '../models/videocall_client_state.dart';
|
||||
|
||||
class VideocallClient with JCClientCallback {
|
||||
VideocallClient(this._config);
|
||||
|
||||
final VideocallSdkConfig _config;
|
||||
JCClient? _client;
|
||||
JCClient? get client => _client;
|
||||
|
||||
// -- Streams --
|
||||
|
||||
final _stateController = StreamController<VideocallClientState>.broadcast();
|
||||
Stream<VideocallClientState> get stateStream => _stateController.stream;
|
||||
|
||||
final _loginResultController =
|
||||
StreamController<({bool success, LoginFailureReason? reason})>.broadcast();
|
||||
Stream<({bool success, LoginFailureReason? reason})> get loginResultStream =>
|
||||
_loginResultController.stream;
|
||||
|
||||
final _logoutController = StreamController<int>.broadcast();
|
||||
Stream<int> get logoutStream => _logoutController.stream;
|
||||
|
||||
final _onlineMessageReceivedController =
|
||||
StreamController<({String userId, String content})>.broadcast();
|
||||
Stream<({String userId, String content})> get onlineMessageReceivedStream =>
|
||||
_onlineMessageReceivedController.stream;
|
||||
|
||||
final _onlineMessageSendResultController =
|
||||
StreamController<({int operationId, bool result})>.broadcast();
|
||||
Stream<({int operationId, bool result})>
|
||||
get onlineMessageSendResultStream =>
|
||||
_onlineMessageSendResultController.stream;
|
||||
|
||||
final _serverMessageController =
|
||||
StreamController<({String type, String params, String message})>
|
||||
.broadcast();
|
||||
Stream<({String type, String params, String message})>
|
||||
get serverMessageStream => _serverMessageController.stream;
|
||||
|
||||
VideocallClientState _state = VideocallClientState.notInitialized;
|
||||
VideocallClientState get state => _state;
|
||||
|
||||
// -- Lifecycle --
|
||||
|
||||
Future<bool> initialize() async {
|
||||
try {
|
||||
_client = await JCClient.create(_config.appKey, this, _config.createParam);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
if (_config.serverAddress.isNotEmpty) {
|
||||
await _client!.setServerAddress(_config.serverAddress);
|
||||
}
|
||||
final clientState = await _client!.getState();
|
||||
_updateState(
|
||||
clientState >= JCClient.STATE_IDLE
|
||||
? VideocallClientState.idle
|
||||
: VideocallClientState.notInitialized,
|
||||
);
|
||||
return _state == VideocallClientState.idle;
|
||||
}
|
||||
|
||||
Future<bool> destroy() async {
|
||||
final result = await JCClient.destroy();
|
||||
if (result) {
|
||||
_client = null;
|
||||
_updateState(VideocallClientState.notInitialized);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// -- Auth --
|
||||
|
||||
Future<bool> login({
|
||||
required String userId,
|
||||
required String password,
|
||||
LoginParam? loginParam,
|
||||
}) async {
|
||||
if (_client == null || _state != VideocallClientState.idle) return false;
|
||||
return _client!.login(userId, password, loginParam ?? LoginParam());
|
||||
}
|
||||
|
||||
Future<bool> relogin({
|
||||
required String userId,
|
||||
required String password,
|
||||
LoginParam? loginParam,
|
||||
}) async {
|
||||
if (_client == null) return false;
|
||||
return _client!.relogin(userId, password, loginParam ?? LoginParam());
|
||||
}
|
||||
|
||||
Future<bool> logout() async {
|
||||
if (_client == null) return false;
|
||||
return _client!.logout();
|
||||
}
|
||||
|
||||
// -- User info --
|
||||
|
||||
Future<String?> getUserId() async => _client?.getUserId();
|
||||
|
||||
Future<String?> getDisplayName() async => _client?.getDisplayName();
|
||||
|
||||
Future<bool> setDisplayName(String displayName) async {
|
||||
if (_client == null) return false;
|
||||
return _client!.setDisplayName(displayName);
|
||||
}
|
||||
|
||||
Future<String?> getAppKey() async => _client?.getAppkey();
|
||||
|
||||
Future<String> getServerUid() async {
|
||||
if (_client == null) return '';
|
||||
return _client!.getServerUid();
|
||||
}
|
||||
|
||||
// -- Server config --
|
||||
|
||||
Future<bool> setServerAddress(String serverAddress) async {
|
||||
if (_client == null) return false;
|
||||
return _client!.setServerAddress(serverAddress);
|
||||
}
|
||||
|
||||
Future<String> getServerAddress() async {
|
||||
if (_client == null) return '';
|
||||
return _client!.getServerAddress();
|
||||
}
|
||||
|
||||
// -- Foreground/background --
|
||||
|
||||
Future<bool> setForeground(bool foreground) async {
|
||||
if (_client == null) return false;
|
||||
return _client!.setForeground(foreground);
|
||||
}
|
||||
|
||||
// -- Online messaging --
|
||||
|
||||
Future<int> sendOnlineMessage({
|
||||
required String userId,
|
||||
required String content,
|
||||
}) async {
|
||||
if (_client == null) return -1;
|
||||
return _client!.sendOnlineMessage(userId, content);
|
||||
}
|
||||
|
||||
// -- Params --
|
||||
|
||||
Future<CreateParam?> getCreateParam() async => _client?.getCreateParam();
|
||||
|
||||
Future<LoginParam?> getLoginParam() async => _client?.getLoginParam();
|
||||
|
||||
Future<int> getState() async {
|
||||
if (_client == null) return JCClient.STATE_NOT_INIT;
|
||||
return _client!.getState();
|
||||
}
|
||||
|
||||
// -- Dispose --
|
||||
|
||||
void dispose() {
|
||||
_stateController.close();
|
||||
_loginResultController.close();
|
||||
_logoutController.close();
|
||||
_onlineMessageReceivedController.close();
|
||||
_onlineMessageSendResultController.close();
|
||||
_serverMessageController.close();
|
||||
}
|
||||
|
||||
// -- Internal --
|
||||
|
||||
void _updateState(VideocallClientState newState) {
|
||||
_state = newState;
|
||||
_stateController.add(newState);
|
||||
}
|
||||
|
||||
VideocallClientState _mapState(int state) {
|
||||
if (state == JCClient.STATE_IDLE) return VideocallClientState.idle;
|
||||
if (state == JCClient.STATE_LOGINING) return VideocallClientState.loggingIn;
|
||||
if (state == JCClient.STATE_LOGINED) return VideocallClientState.loggedIn;
|
||||
if (state == JCClient.STATE_LOGOUTING) {
|
||||
return VideocallClientState.loggingOut;
|
||||
}
|
||||
return VideocallClientState.notInitialized;
|
||||
}
|
||||
|
||||
LoginFailureReason _mapLoginReason(int reason) {
|
||||
if (reason == JCClient.REASON_AUTH) return LoginFailureReason.auth;
|
||||
if (reason == JCClient.REASON_NETWORK) return LoginFailureReason.network;
|
||||
if (reason == JCClient.REASON_APPKEY) return LoginFailureReason.appkey;
|
||||
if (reason == JCClient.REASON_NOUSER) return LoginFailureReason.noUser;
|
||||
if (reason == JCClient.REASON_TIMEOUT) return LoginFailureReason.timeout;
|
||||
if (reason == JCClient.REASON_SERVER_LOGOUT) {
|
||||
return LoginFailureReason.serverLogout;
|
||||
}
|
||||
if (reason == JCClient.REASON_ANOTHER_DEVICE_LOGINED) {
|
||||
return LoginFailureReason.anotherDeviceLoggedIn;
|
||||
}
|
||||
if (reason == JCClient.REASON_TOKEN_MISMATCH) {
|
||||
return LoginFailureReason.tokenMismatch;
|
||||
}
|
||||
if (reason == JCClient.REASON_TOKEN_EXPIRED) {
|
||||
return LoginFailureReason.tokenExpired;
|
||||
}
|
||||
return LoginFailureReason.unknown;
|
||||
}
|
||||
|
||||
// -- JCClientCallback --
|
||||
|
||||
@override
|
||||
void onClientStateChange(int state, int oldState) {
|
||||
_updateState(_mapState(state));
|
||||
}
|
||||
|
||||
@override
|
||||
void onLogin(bool result, int reason) {
|
||||
_loginResultController.add((
|
||||
success: result,
|
||||
reason: result ? null : _mapLoginReason(reason),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void onLogout(int reason) {
|
||||
_logoutController.add(reason);
|
||||
_updateState(VideocallClientState.idle);
|
||||
}
|
||||
|
||||
@override
|
||||
void onOnlineMessageReceive(String userId, String content) {
|
||||
_onlineMessageReceivedController.add((userId: userId, content: content));
|
||||
}
|
||||
|
||||
@override
|
||||
void onOnlineMessageSendResult(int operationId, bool result) {
|
||||
_onlineMessageSendResultController
|
||||
.add((operationId: operationId, result: result));
|
||||
}
|
||||
|
||||
@override
|
||||
void onServerMessageReceive(String type, String params, String message) {
|
||||
_serverMessageController
|
||||
.add((type: type, params: params, message: message));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:jc_sdk/jc_sdk.dart';
|
||||
|
||||
import 'videocall_client.dart';
|
||||
|
||||
class VideocallDeviceService with JCMediaDeviceCallback {
|
||||
VideocallDeviceService({required VideocallClient client}) : _clientRef = client;
|
||||
|
||||
final VideocallClient _clientRef;
|
||||
JCMediaDevice? _mediaDevice;
|
||||
JCMediaDevice? get mediaDevice => _mediaDevice;
|
||||
|
||||
// -- Streams --
|
||||
|
||||
final _cameraUpdateController = StreamController<void>.broadcast();
|
||||
Stream<void> get cameraUpdateStream => _cameraUpdateController.stream;
|
||||
|
||||
final _audioOutputTypeController = StreamController<int>.broadcast();
|
||||
Stream<int> get audioOutputTypeStream => _audioOutputTypeController.stream;
|
||||
|
||||
final _renderReceivedController =
|
||||
StreamController<JCMediaDeviceVideoCanvas>.broadcast();
|
||||
Stream<JCMediaDeviceVideoCanvas> get renderReceivedStream =>
|
||||
_renderReceivedController.stream;
|
||||
|
||||
final _renderStartController =
|
||||
StreamController<JCMediaDeviceVideoCanvas>.broadcast();
|
||||
Stream<JCMediaDeviceVideoCanvas> get renderStartStream =>
|
||||
_renderStartController.stream;
|
||||
|
||||
final _videoErrorController =
|
||||
StreamController<JCMediaDeviceVideoCanvas>.broadcast();
|
||||
Stream<JCMediaDeviceVideoCanvas> get videoErrorStream =>
|
||||
_videoErrorController.stream;
|
||||
|
||||
final _audioErrorController = StreamController<bool>.broadcast();
|
||||
Stream<bool> get audioErrorStream => _audioErrorController.stream;
|
||||
|
||||
final _audioResumeController = StreamController<void>.broadcast();
|
||||
Stream<void> get audioResumeStream => _audioResumeController.stream;
|
||||
|
||||
// -- Lifecycle --
|
||||
|
||||
Future<bool> initialize() async {
|
||||
final client = _clientRef.client;
|
||||
if (client == null) return false;
|
||||
_mediaDevice = await JCMediaDevice.create(client, this);
|
||||
return _mediaDevice != null;
|
||||
}
|
||||
|
||||
Future<bool> destroy() async => JCMediaDevice.destroy();
|
||||
|
||||
// -- Camera --
|
||||
|
||||
Future<bool> isCameraOpen() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.isCameraOpen();
|
||||
}
|
||||
|
||||
Future<JCMediaDeviceCamera?> getCamera() async => _mediaDevice?.getCamera();
|
||||
|
||||
Future<List<JCMediaDeviceCamera>?> getCameras() async =>
|
||||
_mediaDevice?.getCameras();
|
||||
|
||||
Future<List<JCMediaDeviceCamera>?> getExtCameras() async =>
|
||||
_mediaDevice?.getExtCameras();
|
||||
|
||||
Future<JCMediaDeviceCamera?> getDefaultCamera() async =>
|
||||
_mediaDevice?.getDefaultCamera();
|
||||
|
||||
Future<bool> setDefaultCamera(String cameraId) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setDefaultCamera(cameraId);
|
||||
}
|
||||
|
||||
Future<bool> startCamera() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.startCamera();
|
||||
}
|
||||
|
||||
Future<bool> stopCamera() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.stopCamera();
|
||||
}
|
||||
|
||||
Future<bool> switchCamera({JCMediaDeviceCamera? camera}) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.switchCamera(camera: camera);
|
||||
}
|
||||
|
||||
Future<bool> setCameraProperty(int width, int height, int frameRate) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setCameraProperty(width, height, frameRate);
|
||||
}
|
||||
|
||||
Future<bool> setScreenCaptureProperty(
|
||||
int width, int height, int frameRate) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setScreenCaptureProperty(width, height, frameRate);
|
||||
}
|
||||
|
||||
Future<int> getCameraType(int cameraIndex) async {
|
||||
if (_mediaDevice == null) return JCMediaDevice.CAMERA_NONE;
|
||||
return _mediaDevice!.getCameraType(cameraIndex);
|
||||
}
|
||||
|
||||
// -- Exposure --
|
||||
|
||||
Future<int> getMinExposureCompensation() async {
|
||||
if (_mediaDevice == null) return 0;
|
||||
return _mediaDevice!.getMinExposureCompensation();
|
||||
}
|
||||
|
||||
Future<int> getMaxExposureCompensation() async {
|
||||
if (_mediaDevice == null) return 0;
|
||||
return _mediaDevice!.getMaxExposureCompensation();
|
||||
}
|
||||
|
||||
Future<double> getExposureCompensationStep() async {
|
||||
if (_mediaDevice == null) return 0;
|
||||
return _mediaDevice!.getExposureCompensationStep();
|
||||
}
|
||||
|
||||
Future<bool> setIOSExposureCompensation(double level) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setIOSExposureCompensation(level);
|
||||
}
|
||||
|
||||
Future<bool> setExposureCompensation(int level) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setExposureCompensation(level);
|
||||
}
|
||||
|
||||
// -- Flash --
|
||||
|
||||
Future<bool> isCameraFlashSupported() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.isCameraFlashSupported();
|
||||
}
|
||||
|
||||
Future<bool> enableFlash(bool enable) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.enableFlash(enable);
|
||||
}
|
||||
|
||||
// -- Focus/Zoom --
|
||||
|
||||
Future<bool> handleFocusMetering(
|
||||
JCMediaDeviceVideoCanvas canvas, double xPercent, double yPercent) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.handleFocusMetering(canvas, xPercent, yPercent);
|
||||
}
|
||||
|
||||
Future<bool> setCameraZoomRatio(double zoomRatio) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setCameraZoomRatio(zoomRatio);
|
||||
}
|
||||
|
||||
Future<double> getCameraMaxZoom() async {
|
||||
if (_mediaDevice == null) return 1.0;
|
||||
return _mediaDevice!.getCameraMaxZoom();
|
||||
}
|
||||
|
||||
Future<int> getCameraCurrentZoom() async {
|
||||
if (_mediaDevice == null) return 0;
|
||||
return _mediaDevice!.getCameraCurrentZoom();
|
||||
}
|
||||
|
||||
// -- Speaker --
|
||||
|
||||
Future<bool> isSpeakerOn() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.isSpeakerOn();
|
||||
}
|
||||
|
||||
Future<bool> enableSpeaker(bool enable) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.enableSpeaker(enable);
|
||||
}
|
||||
|
||||
Future<bool> getDefaultSpeakerOn() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.getDefaultSpeakerOn();
|
||||
}
|
||||
|
||||
Future<bool> setDefaultSpeakerOn(bool state) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setDefaultSpeakerOn(state);
|
||||
}
|
||||
|
||||
Future<int> getAudioRouteType() async {
|
||||
if (_mediaDevice == null) return 0;
|
||||
return _mediaDevice!.getAudioRouteType();
|
||||
}
|
||||
|
||||
// -- Audio --
|
||||
|
||||
Future<bool> isAudioStart() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.isAudioStart();
|
||||
}
|
||||
|
||||
Future<bool> startAudio() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.startAudio();
|
||||
}
|
||||
|
||||
Future<bool> stopAudio() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.stopAudio();
|
||||
}
|
||||
|
||||
Future<bool> setAudioAecMode(int mode) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setAudioAecMode(mode);
|
||||
}
|
||||
|
||||
Future<bool> autoStartAudioOutput(bool enable) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.autoStartAudioOutput(enable);
|
||||
}
|
||||
|
||||
Future<bool> autoStartAudioInput(bool enable) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.autoStartAudioInput(enable);
|
||||
}
|
||||
|
||||
Future<bool> setDeviceAudioAutoInput(bool auto) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setDeviceAudioAutoInput(auto);
|
||||
}
|
||||
|
||||
Future<bool> setDeviceAudioAutoOutput(bool auto) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setDeviceAudioAutoOutput(auto);
|
||||
}
|
||||
|
||||
Future<bool> getUseInternalAudioDeviceLogic() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.getUseInternalAudioDeviceLogic();
|
||||
}
|
||||
|
||||
Future<JCMediaDeviceAudioParam?> getAudioParam() async =>
|
||||
_mediaDevice?.getAudioParam();
|
||||
|
||||
// -- Volume --
|
||||
|
||||
Future<int> getOutputVolume() async {
|
||||
if (_mediaDevice == null) return 0;
|
||||
return _mediaDevice!.getOutputVolume();
|
||||
}
|
||||
|
||||
Future<int> getInputVolume() async {
|
||||
if (_mediaDevice == null) return 0;
|
||||
return _mediaDevice!.getInputVolume();
|
||||
}
|
||||
|
||||
bool addVolumeCallback(JCMediaVolumeCallback callback) {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.addVolumeCallback(callback);
|
||||
}
|
||||
|
||||
bool removeVolumeCallback(JCMediaVolumeCallback callback) {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.removeVolumeCallback(callback);
|
||||
}
|
||||
|
||||
// -- Video rendering --
|
||||
|
||||
Future<JCMediaDeviceVideoCanvas?> startCameraVideo(
|
||||
{int renderType = JCMediaDevice.RENDER_FULL_AUTO}) async {
|
||||
return _mediaDevice?.startCameraVideo(renderType);
|
||||
}
|
||||
|
||||
Future<JCMediaDeviceVideoCanvas?> startVideo(
|
||||
String? videoSource, int renderType) async {
|
||||
return _mediaDevice?.startVideo(videoSource, renderType);
|
||||
}
|
||||
|
||||
Future<bool> stopVideo(JCMediaDeviceVideoCanvas canvas) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.stopVideo(canvas);
|
||||
}
|
||||
|
||||
// -- Video file (custom capture) --
|
||||
|
||||
Future<bool> isVideoFileOpen() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.isVideoFileOpen();
|
||||
}
|
||||
|
||||
Future<String> getVideoFileId() async {
|
||||
if (_mediaDevice == null) return '';
|
||||
return _mediaDevice!.getVideoFileId();
|
||||
}
|
||||
|
||||
Future<bool> startVideoFile() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.startVideoFile();
|
||||
}
|
||||
|
||||
Future<bool> setVideoFileFrame(Uint8List data, int format, int width,
|
||||
int height, int angle, int mirror, bool keyFrame) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!
|
||||
.setVideoFileFrame(data, format, width, height, angle, mirror, keyFrame);
|
||||
}
|
||||
|
||||
Future<bool> stopVideoFile() async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.stopVideoFile();
|
||||
}
|
||||
|
||||
// -- Video angle --
|
||||
|
||||
Future<bool> setVideoAngle(int angle) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setVideoAngle(angle);
|
||||
}
|
||||
|
||||
Future<int> getVideoAngle() async {
|
||||
if (_mediaDevice == null) return 0;
|
||||
return _mediaDevice!.getVideoAngle();
|
||||
}
|
||||
|
||||
// -- Frame callbacks --
|
||||
|
||||
Future<bool> setAudioFrameCallback(JCAudioFrameCallback? callback) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setAudioFrameCallback(callback);
|
||||
}
|
||||
|
||||
Future<bool> setVideoFrameCallback(JCVideoFrameCallback? callback) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.setVideoFrameCallback(callback);
|
||||
}
|
||||
|
||||
// -- Custom audio --
|
||||
|
||||
Future<bool> inputCustomAudioData(int sampleRateHz, int channels,
|
||||
Uint8List byteBuffer, int playDelayMS, int recDelayMS, int clockDrift) async {
|
||||
if (_mediaDevice == null) return false;
|
||||
return _mediaDevice!.inputCustomAudioData(
|
||||
sampleRateHz, channels, byteBuffer, playDelayMS, recDelayMS, clockDrift);
|
||||
}
|
||||
|
||||
Future<Uint8List?> getAudioOutputData(int sampleRateHz, int channels) async {
|
||||
return _mediaDevice?.getAudioOutputData(sampleRateHz, channels);
|
||||
}
|
||||
|
||||
// -- Dispose --
|
||||
|
||||
void dispose() {
|
||||
_cameraUpdateController.close();
|
||||
_audioOutputTypeController.close();
|
||||
_renderReceivedController.close();
|
||||
_renderStartController.close();
|
||||
_videoErrorController.close();
|
||||
_audioErrorController.close();
|
||||
_audioResumeController.close();
|
||||
}
|
||||
|
||||
// -- JCMediaDeviceCallback --
|
||||
|
||||
@override
|
||||
void onCameraUpdate() => _cameraUpdateController.add(null);
|
||||
|
||||
@override
|
||||
void onAudioOutputTypeChange(int audioRouteType) =>
|
||||
_audioOutputTypeController.add(audioRouteType);
|
||||
|
||||
@override
|
||||
void onRenderReceived(JCMediaDeviceVideoCanvas canvas) =>
|
||||
_renderReceivedController.add(canvas);
|
||||
|
||||
@override
|
||||
void onRenderStart(JCMediaDeviceVideoCanvas canvas) =>
|
||||
_renderStartController.add(canvas);
|
||||
|
||||
@override
|
||||
void onVideoError(JCMediaDeviceVideoCanvas canvas) =>
|
||||
_videoErrorController.add(canvas);
|
||||
|
||||
@override
|
||||
void onAudioError(bool background) => _audioErrorController.add(background);
|
||||
|
||||
@override
|
||||
void onAudioResume() => _audioResumeController.add(null);
|
||||
|
||||
@override
|
||||
void onNeedKeyFrame() {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:jc_sdk/jc_sdk.dart';
|
||||
|
||||
class VideocallLogService {
|
||||
VideocallLogService._();
|
||||
|
||||
static Future<bool> uploadLog(String reason) => JCLog.uploadLog(reason);
|
||||
static Future<bool> info(String type, String format) =>
|
||||
JCLog.info(type, format);
|
||||
static Future<bool> error(String type, String format) =>
|
||||
JCLog.error(type, format);
|
||||
static Future<bool> debug(String type, String format) =>
|
||||
JCLog.debug(type, format);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:jc_sdk/jc_sdk.dart';
|
||||
|
||||
class VideocallNetService with JCNetCallback {
|
||||
// -- Streams --
|
||||
|
||||
final _netChangeController =
|
||||
StreamController<({int newNetType, int oldNetType})>.broadcast();
|
||||
Stream<({int newNetType, int oldNetType})> get netChangeStream =>
|
||||
_netChangeController.stream;
|
||||
|
||||
// -- Lifecycle --
|
||||
|
||||
void initialize() {
|
||||
JCNet.getInstance().addCallback(this);
|
||||
}
|
||||
|
||||
void uninitialize() {
|
||||
JCNet.getInstance().removeCallback(this);
|
||||
}
|
||||
|
||||
// -- Network info --
|
||||
|
||||
Future<int> getNetType() async => JCNet.getInstance().getNetType();
|
||||
|
||||
Future<bool> hasNet() async => JCNet.getInstance().hasNet();
|
||||
|
||||
// -- Dispose --
|
||||
|
||||
void dispose() {
|
||||
uninitialize();
|
||||
_netChangeController.close();
|
||||
}
|
||||
|
||||
// -- JCNetCallback --
|
||||
|
||||
@override
|
||||
void onNetChange(int newNetType, int oldNetType) {
|
||||
_netChangeController.add((newNetType: newNetType, oldNetType: oldNetType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:jc_sdk/jc_sdk.dart';
|
||||
|
||||
import 'videocall_client.dart';
|
||||
|
||||
class VideocallPushService {
|
||||
VideocallPushService({required VideocallClient client}) : _clientRef = client;
|
||||
|
||||
final VideocallClient _clientRef;
|
||||
JCPush? _push;
|
||||
JCPush? get push => _push;
|
||||
|
||||
// -- Lifecycle --
|
||||
|
||||
Future<bool> initialize() async {
|
||||
final client = _clientRef.client;
|
||||
if (client == null) return false;
|
||||
_push = await JCPush.create(client);
|
||||
return _push != null;
|
||||
}
|
||||
|
||||
Future<void> destroy() async {
|
||||
await JCPush.destroy();
|
||||
_push = null;
|
||||
}
|
||||
|
||||
// -- Push --
|
||||
|
||||
Future<bool> addPushInfo(JCPushTemplate info) async {
|
||||
if (_push == null) return false;
|
||||
return _push!.addPushInfo(info);
|
||||
}
|
||||
|
||||
Future<bool> addPushTemplate(String data) async {
|
||||
if (_push == null) return false;
|
||||
return _push!.addPushTemplate(data);
|
||||
}
|
||||
|
||||
// -- Dispose --
|
||||
|
||||
void dispose() {
|
||||
_push = null;
|
||||
}
|
||||
}
|
||||
60
packages/videocall_sdk/lib/videocall_sdk.dart
Normal file
60
packages/videocall_sdk/lib/videocall_sdk.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
/// SaveFamily video call SDK wrapper around Juphoon jc_sdk.
|
||||
library;
|
||||
|
||||
// Config
|
||||
export 'src/config/videocall_sdk_config.dart';
|
||||
|
||||
// Models
|
||||
export 'src/models/call_direction.dart';
|
||||
export 'src/models/call_state.dart';
|
||||
export 'src/models/login_failure_reason.dart';
|
||||
export 'src/models/videocall_client_state.dart';
|
||||
export 'src/models/videocall_item.dart';
|
||||
|
||||
// Services
|
||||
export 'src/services/videocall_client.dart';
|
||||
export 'src/services/videocall_call_service.dart';
|
||||
export 'src/services/videocall_device_service.dart';
|
||||
export 'src/services/videocall_channel_service.dart';
|
||||
export 'src/services/videocall_push_service.dart';
|
||||
export 'src/services/videocall_net_service.dart';
|
||||
export 'src/services/videocall_log_service.dart';
|
||||
|
||||
// Manager
|
||||
export 'src/manager/videocall_sdk_manager.dart';
|
||||
|
||||
// DI
|
||||
export 'src/di/videocall_sdk_module.dart';
|
||||
|
||||
// Providers
|
||||
export 'src/providers/videocall_providers.dart';
|
||||
|
||||
// Re-export jc_sdk types needed by consumers
|
||||
export 'package:jc_sdk/jc_sdk.dart'
|
||||
show
|
||||
CallParam,
|
||||
ChangeParam,
|
||||
CreateParam,
|
||||
LoginParam,
|
||||
MediaConfig,
|
||||
JoinParam,
|
||||
RecordParam,
|
||||
ScreenShareParam,
|
||||
SipParam,
|
||||
PropChangeParam,
|
||||
ChannelChangeParam,
|
||||
JCCallItem,
|
||||
JCMediaChannelParticipant,
|
||||
JCMediaChannelQueryInfo,
|
||||
JCMediaDeviceVideoCanvas,
|
||||
JCMediaDeviceCamera,
|
||||
JCMediaDeviceAudioParam,
|
||||
RenderMirrorType,
|
||||
JCPushTemplate,
|
||||
JCAudioFrameCallback,
|
||||
JCVideoFrameCallback,
|
||||
JCMediaVolumeCallback,
|
||||
JCClient,
|
||||
JCCall,
|
||||
JCMediaDevice,
|
||||
JCMediaChannel;
|
||||
21
packages/videocall_sdk/pubspec.yaml
Normal file
21
packages/videocall_sdk/pubspec.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
name: videocall_sdk
|
||||
description: Wrapper around Juphoon jc_sdk for video calling in SaveFamily.
|
||||
version: 0.0.1
|
||||
resolution: workspace
|
||||
homepage:
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
jc_sdk: ^2.16.5
|
||||
flutter_riverpod: ^3.0.3
|
||||
get_it: ^9.0.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^5.0.0
|
||||
@@ -1004,6 +1004,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
jc_sdk:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jc_sdk
|
||||
sha256: "017148c51e6181870507d429b2e2b52df1b13168ebd9590b3a3fa86ab09274ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.16.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -36,6 +36,7 @@ workspace:
|
||||
- packages/sf_shared
|
||||
- packages/sf_tracking
|
||||
- packages/utils
|
||||
- packages/videocall_sdk
|
||||
|
||||
dependencies:
|
||||
flutter_secure_storage: ^9.2.4
|
||||
|
||||
Reference in New Issue
Block a user