Skip to content

Commit

Permalink
Merge branch 'main' into improve-reconnect-logic-for-websocket
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudwebrtc committed Dec 14, 2023
2 parents 94269b4 + 78d0ed2 commit 6cdc4ff
Show file tree
Hide file tree
Showing 13 changed files with 723 additions and 188 deletions.
16 changes: 16 additions & 0 deletions example/ios/LiveKit Broadcast Extension/SampleHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class SampleHandler: RPBroadcastSampleHandler {

DarwinNotificationCenter.shared.postNotification(.broadcastStarted)
openConnection()
startReplayKit()
}

override func broadcastPaused() {
Expand All @@ -57,6 +58,8 @@ class SampleHandler: RPBroadcastSampleHandler {
// User has requested to finish the broadcast.
DarwinNotificationCenter.shared.postNotification(.broadcastStopped)
clientConnection?.close()
closeReplayKit()

}

override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
Expand Down Expand Up @@ -100,4 +103,17 @@ private extension SampleHandler {

timer.resume()
}

func startReplayKit() {
let group=UserDefaults(suiteName: Constants.appGroupIdentifier)
group!.set(false, forKey: "closeReplayKitFromNative")
group!.set(false, forKey: "closeReplayKitFromFlutter")
group!.set(true, forKey: "hasSampleBroadcast")
}

func closeReplayKit() {
let group = UserDefaults(suiteName: Constants.appGroupIdentifier)
group!.set(true, forKey:"closeReplayKitFromNative")
group!.set(false, forKey: "hasSampleBroadcast")
}
}
71 changes: 64 additions & 7 deletions example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,68 @@ import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

var replayKitChannel: FlutterMethodChannel! = nil
var observeTimer: Timer?
var hasEmittedFirstSample = false;

override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
guard let controller = window?.rootViewController as? FlutterViewController else {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

replayKitChannel = FlutterMethodChannel(name: "io.livekit.example.flutter/replaykit-channel",binaryMessenger: controller.binaryMessenger)

replayKitChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
self.handleReplayKitFromFlutter(result: result, call:call)
})

GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

func handleReplayKitFromFlutter(result:FlutterResult, call: FlutterMethodCall){
switch (call.method) {
case "startReplayKit":
self.hasEmittedFirstSample = false
let group=UserDefaults(suiteName: "group.io.livekit.example.flutter")
group!.set(false, forKey: "closeReplayKitFromNative")
group!.set(false, forKey: "closeReplayKitFromFlutter")
self.observeReplayKitStateChanged()
break
case "closeReplayKit":
let group=UserDefaults(suiteName: "group.io.livekit.example.flutter")
group!.set(true,forKey: "closeReplayKitFromFlutter")
result(true)
break
default:
return result(FlutterMethodNotImplemented)
}
}

func observeReplayKitStateChanged(){
if (self.observeTimer != nil) {
return
}

let group=UserDefaults(suiteName: "group.io.livekit.example.flutter")
self.observeTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
let closeReplayKitFromNative=group!.bool(forKey: "closeReplayKitFromNative")
let hasSampleBroadcast=group!.bool(forKey: "hasSampleBroadcast")

if (closeReplayKitFromNative) {
self.hasEmittedFirstSample = false
self.replayKitChannel.invokeMethod("closeReplayKitFromNative", arguments: true)
} else if (hasSampleBroadcast) {
if (!self.hasEmittedFirstSample) {
self.hasEmittedFirstSample = true
self.replayKitChannel.invokeMethod("hasSampleBroadcast", arguments: true)
}
}
}
}
}
40 changes: 40 additions & 0 deletions example/lib/method_channels/replay_kit_channel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Dart imports:
import 'dart:io';

// Flutter imports:
import 'package:flutter/services.dart';
import 'package:livekit_client/livekit_client.dart';

class ReplayKitChannel {
static const String kReplayKitChannel =
'io.livekit.example.flutter/replaykit-channel';

static const MethodChannel _replayKitChannel =
MethodChannel(kReplayKitChannel);

static void listenMethodChannel(Room room) {
_replayKitChannel.setMethodCallHandler((call) async {
if (call.method == 'closeReplayKitFromNative') {
if (!(room.localParticipant?.isScreenShareEnabled() ?? false)) return;

await room.localParticipant?.setScreenShareEnabled(false);
} else if (call.method == 'hasSampleBroadcast') {
if (room.localParticipant?.isScreenShareEnabled() ?? true) return;

await room.localParticipant?.setScreenShareEnabled(true);
}
});
}

static void startReplayKit() {
if (!Platform.isIOS) return;

_replayKitChannel.invokeMethod('startReplayKit');
}

static void closeReplayKit() {
if (!Platform.isIOS) return;

_replayKitChannel.invokeMethod('closeReplayKit');
}
}
139 changes: 20 additions & 119 deletions example/lib/pages/connect.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_example/pages/prejoin.dart';
import 'package:livekit_example/widgets/text_field.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:permission_handler/permission_handler.dart';

import '../exts.dart';
import 'room.dart';

class ConnectPage extends StatefulWidget {
//
Expand All @@ -25,7 +25,6 @@ class _ConnectPageState extends State<ConnectPage> {
static const _storeKeySimulcast = 'simulcast';
static const _storeKeyAdaptiveStream = 'adaptive-stream';
static const _storeKeyDynacast = 'dynacast';
static const _storeKeyFastConnect = 'fast-connect';
static const _storeKeyE2EE = 'e2ee';
static const _storeKeySharedKey = 'shared-key';
static const _storeKeyMultiCodec = 'multi-codec';
Expand All @@ -38,12 +37,10 @@ class _ConnectPageState extends State<ConnectPage> {
bool _adaptiveStream = true;
bool _dynacast = true;
bool _busy = false;
bool _fastConnect = false;
bool _e2ee = false;
bool _multiCodec = false;
bool _autoSubscribe = false;
String _preferredCodec = 'Preferred Codec';
String _backupCodec = 'VP8';

@override
void initState() {
Expand Down Expand Up @@ -99,7 +96,6 @@ class _ConnectPageState extends State<ConnectPage> {
_simulcast = prefs.getBool(_storeKeySimulcast) ?? true;
_adaptiveStream = prefs.getBool(_storeKeyAdaptiveStream) ?? true;
_dynacast = prefs.getBool(_storeKeyDynacast) ?? true;
_fastConnect = prefs.getBool(_storeKeyFastConnect) ?? false;
_e2ee = prefs.getBool(_storeKeyE2EE) ?? false;
_multiCodec = prefs.getBool(_storeKeyMultiCodec) ?? false;
_autoSubscribe = prefs.getBool(_storeKeyAutoSubscribe) ?? true;
Expand All @@ -115,7 +111,6 @@ class _ConnectPageState extends State<ConnectPage> {
await prefs.setBool(_storeKeySimulcast, _simulcast);
await prefs.setBool(_storeKeyAdaptiveStream, _adaptiveStream);
await prefs.setBool(_storeKeyDynacast, _dynacast);
await prefs.setBool(_storeKeyFastConnect, _fastConnect);
await prefs.setBool(_storeKeyE2EE, _e2ee);
await prefs.setBool(_storeKeyMultiCodec, _multiCodec);
await prefs.setBool(_storeKeyAutoSubscribe, _autoSubscribe);
Expand All @@ -134,63 +129,28 @@ class _ConnectPageState extends State<ConnectPage> {
print('Connecting with url: ${_uriCtrl.text}, '
'token: ${_tokenCtrl.text}...');

E2EEOptions? e2eeOptions;
if (_e2ee) {
final keyProvider = await BaseKeyProvider.create();
e2eeOptions = E2EEOptions(keyProvider: keyProvider);
var sharedKey = _sharedKeyCtrl.text;
await keyProvider.setSharedKey(sharedKey);
}

String preferredCodec = 'VP8';
if (_preferredCodec != 'Preferred Codec') {
preferredCodec = _preferredCodec;
}

// create new room
final room = Room(
connectOptions: ConnectOptions(
autoSubscribe: _autoSubscribe,
),
roomOptions: RoomOptions(
adaptiveStream: _adaptiveStream,
dynacast: _dynacast,
defaultAudioPublishOptions: const AudioPublishOptions(
dtx: true,
),
defaultVideoPublishOptions: VideoPublishOptions(
simulcast: _simulcast,
videoCodec: preferredCodec,
),
defaultScreenShareCaptureOptions: const ScreenShareCaptureOptions(
useiOSBroadcastExtension: true,
params: VideoParametersPresets.screenShareH1080FPS30),
e2eeOptions: e2eeOptions,
defaultCameraCaptureOptions: const CameraCaptureOptions(
maxFrameRate: 30,
params: VideoParametersPresets.h720_169,
),
));

// Create a Listener before connecting
final listener = room.createListener();

// Try to connect to the room
// This will throw an Exception if it fails for any reason.
await room.connect(
_uriCtrl.text,
_tokenCtrl.text,
fastConnectOptions: _fastConnect
? FastConnectOptions(
microphone: const TrackOption(enabled: true),
camera: const TrackOption(enabled: true),
)
: null,
);
var url = _uriCtrl.text;
var token = _tokenCtrl.text;
var e2eeKey = _sharedKeyCtrl.text;

await Navigator.push<void>(
ctx,
MaterialPageRoute(builder: (_) => RoomPage(room, listener)),
MaterialPageRoute(
builder: (_) => PreJoinPage(
args: JoinArgs(
url: url,
token: token,
e2ee: _e2ee,
e2eeKey: e2eeKey,
simulcast: _simulcast,
autoSubscribe: _autoSubscribe,
adaptiveStream: _adaptiveStream,
dynacast: _dynacast,
preferredCodec: _preferredCodec,
enableBackupVideoCodec:
['VP9', 'AV1'].contains(_preferredCodec),
),
)),
);
} catch (error) {
print('Could not connect $error');
Expand Down Expand Up @@ -237,13 +197,6 @@ class _ConnectPageState extends State<ConnectPage> {
});
}

void _setFastConnect(bool? value) async {
if (value == null || _fastConnect == value) return;
setState(() {
_fastConnect = value;
});
}

void _setMultiCodec(bool? value) async {
if (value == null || _multiCodec == value) return;
setState(() {
Expand Down Expand Up @@ -345,19 +298,6 @@ class _ConnectPageState extends State<ConnectPage> {
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Fast Connect'),
Switch(
value: _fastConnect,
onChanged: (value) => _setFastConnect(value),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Row(
Expand Down Expand Up @@ -423,45 +363,6 @@ class _ConnectPageState extends State<ConnectPage> {
}).toList(),
)
])),
if (_multiCodec &&
_preferredCodec != 'Preferred Codec' &&
['av1', 'vp9'].contains(_preferredCodec.toLowerCase()))
Padding(
padding: const EdgeInsets.only(bottom: 25),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Backup Codec:'),
DropdownButton<String>(
value: _backupCodec,
icon: const Icon(
Icons.arrow_drop_down,
color: Colors.blue,
),
elevation: 16,
style: const TextStyle(color: Colors.blue),
underline: Container(
height: 2,
color: Colors.blueAccent,
),
onChanged: (String? value) {
// This is called when the user selects an item.
setState(() {
_backupCodec = value!;
});
},
items: [
'Backup Codec',
'VP8',
'H264'
].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)
])),
ElevatedButton(
onPressed: _busy ? null : () => _connect(context),
child: Row(
Expand Down
Loading

0 comments on commit 6cdc4ff

Please sign in to comment.