Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/flutter_webrtc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.3

* Fix `RTCPeerConnection.close()` to be idempotent; calling it more than once no longer throws an error.

## 0.2.2

* Support x86_64
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_webrtc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ For other Tizen devices :
```yaml
dependencies:
flutter_webrtc: ^1.4.1
flutter_webrtc_tizen: ^0.2.2
flutter_webrtc_tizen: ^0.2.3
```

## Functionality
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Copyright 2024 Samsung Electronics Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:typed_data';

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:integration_test/integration_test.dart';

const Map<String, dynamic> _kConfig = {
'iceServers': <Map<String, dynamic>>[],
};

Future<void> _negotiate(
RTCPeerConnection pc1,
RTCPeerConnection pc2,
) async {
pc1.onIceCandidate = (c) => pc2.addCandidate(c);
pc2.onIceCandidate = (c) => pc1.addCandidate(c);

final offer = await pc1.createOffer();
await pc1.setLocalDescription(offer);
await pc2.setRemoteDescription(offer);
final answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
await pc1.setRemoteDescription(answer);
}

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

group('RTCPeerConnection', () {
testWidgets('creates with empty ice servers', (WidgetTester tester) async {
final pc = await createPeerConnection(_kConfig);
expect(pc, isNotNull);
await pc.close();
}, timeout: const Timeout(Duration(seconds: 10)));

testWidgets('createOffer returns valid SDP', (WidgetTester tester) async {
final pc = await createPeerConnection(_kConfig);
final offer = await pc.createOffer();
expect(offer.type, equals('offer'));
expect(offer.sdp, isNotEmpty);
await pc.close();
}, timeout: const Timeout(Duration(seconds: 10)));

testWidgets('setLocalDescription succeeds', (WidgetTester tester) async {
final pc = await createPeerConnection(_kConfig);
final offer = await pc.createOffer();
await expectLater(pc.setLocalDescription(offer), completes);
await pc.close();
}, timeout: const Timeout(Duration(seconds: 10)));

testWidgets('createAnswer returns valid SDP', (WidgetTester tester) async {
final pc1 = await createPeerConnection(_kConfig);
final pc2 = await createPeerConnection(_kConfig);
final offer = await pc1.createOffer();
await pc2.setRemoteDescription(offer);
final answer = await pc2.createAnswer();
expect(answer.type, equals('answer'));
expect(answer.sdp, isNotEmpty);
await pc1.close();
await pc2.close();
}, timeout: const Timeout(Duration(seconds: 10)));

testWidgets('close is idempotent', (WidgetTester tester) async {
final pc = await createPeerConnection(_kConfig);
await pc.close();
await expectLater(pc.close(), completes);
}, timeout: const Timeout(Duration(seconds: 10)));
});

group('RTCDataChannel', () {
testWidgets('createDataChannel returns channel with correct label',
(WidgetTester tester) async {
final pc = await createPeerConnection(_kConfig);
final dc = await pc.createDataChannel('test-label', RTCDataChannelInit());
expect(dc.label, equals('test-label'));
await dc.close();
await pc.close();
}, timeout: const Timeout(Duration(seconds: 10)));

testWidgets('loopback text message exchange', (WidgetTester tester) async {
final pc1 = await createPeerConnection(_kConfig);
final pc2 = await createPeerConnection(_kConfig);

final dc2Completer = Completer<RTCDataChannel>();
pc2.onDataChannel = (channel) {
if (!dc2Completer.isCompleted) dc2Completer.complete(channel);
};

final dc1 = await pc1.createDataChannel(
'loopback',
RTCDataChannelInit()..ordered = true,
);

await _negotiate(pc1, pc2);

final dc2 =
await dc2Completer.future.timeout(const Duration(seconds: 10));

final dc1Open = Completer<void>();
final dc2Open = Completer<void>();
if (dc1.state == RTCDataChannelState.RTCDataChannelOpen) {
dc1Open.complete();
} else {
dc1.onDataChannelState = (s) {
if (s == RTCDataChannelState.RTCDataChannelOpen &&
!dc1Open.isCompleted) {
dc1Open.complete();
}
};
}
if (dc2.state == RTCDataChannelState.RTCDataChannelOpen) {
dc2Open.complete();
} else {
dc2.onDataChannelState = (s) {
if (s == RTCDataChannelState.RTCDataChannelOpen &&
!dc2Open.isCompleted) {
dc2Open.complete();
}
};
}

await Future.wait([
dc1Open.future.timeout(const Duration(seconds: 10)),
dc2Open.future.timeout(const Duration(seconds: 10)),
]);
Comment thread
seungsoo47 marked this conversation as resolved.

final dc2Received = Completer<String>();
dc2.onMessage = (msg) {
if (!dc2Received.isCompleted) dc2Received.complete(msg.text);
};
await dc1.send(RTCDataChannelMessage('hello from dc1'));
expect(
await dc2Received.future.timeout(const Duration(seconds: 5)),
equals('hello from dc1'),
);

final dc1Received = Completer<String>();
dc1.onMessage = (msg) {
if (!dc1Received.isCompleted) dc1Received.complete(msg.text);
};
await dc2.send(RTCDataChannelMessage('hello from dc2'));
expect(
await dc1Received.future.timeout(const Duration(seconds: 5)),
equals('hello from dc2'),
);

await dc1.close();
await dc2.close();
await pc1.close();
await pc2.close();
}, timeout: const Timeout(Duration(seconds: 60)));

testWidgets('loopback binary message exchange',
(WidgetTester tester) async {
final pc1 = await createPeerConnection(_kConfig);
final pc2 = await createPeerConnection(_kConfig);

final dc2Completer = Completer<RTCDataChannel>();
pc2.onDataChannel = (channel) {
if (!dc2Completer.isCompleted) dc2Completer.complete(channel);
};

final dc1 = await pc1.createDataChannel(
'binary',
RTCDataChannelInit()..ordered = true,
);

await _negotiate(pc1, pc2);

final dc2 =
await dc2Completer.future.timeout(const Duration(seconds: 10));

final dc1Open = Completer<void>();
final dc2Open = Completer<void>();
if (dc1.state == RTCDataChannelState.RTCDataChannelOpen) {
dc1Open.complete();
} else {
dc1.onDataChannelState = (s) {
if (s == RTCDataChannelState.RTCDataChannelOpen &&
!dc1Open.isCompleted) {
dc1Open.complete();
}
};
}
if (dc2.state == RTCDataChannelState.RTCDataChannelOpen) {
dc2Open.complete();
} else {
dc2.onDataChannelState = (s) {
if (s == RTCDataChannelState.RTCDataChannelOpen &&
!dc2Open.isCompleted) {
dc2Open.complete();
}
};
}

await Future.wait([
dc1Open.future.timeout(const Duration(seconds: 10)),
dc2Open.future.timeout(const Duration(seconds: 10)),
]);
Comment thread
seungsoo47 marked this conversation as resolved.

final payload = Uint8List.fromList([1, 2, 3, 4, 5]);

final dc2Received = Completer<Uint8List>();
dc2.onMessage = (msg) {
if (!dc2Received.isCompleted) dc2Received.complete(msg.binary);
};
await dc1.send(RTCDataChannelMessage.fromBinary(payload));
expect(
await dc2Received.future.timeout(const Duration(seconds: 5)),
equals(payload),
);

await dc1.close();
await dc2.close();
await pc1.close();
await pc2.close();
}, timeout: const Timeout(Duration(seconds: 60)));
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,14 @@ dependencies:
web: ^1.0.0

dev_dependencies:
flutter_driver:
sdk: flutter
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
integration_test_tizen:
path: ../../../integration_test/
pedantic: ^1.11.0

flutter:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import 'package:integration_test/integration_test_driver.dart';

Future<void> main() => integrationDriver();
2 changes: 1 addition & 1 deletion packages/flutter_webrtc/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: flutter_webrtc_tizen
homepage: https://github.com/flutter-tizen/plugins
description: Flutter WebRTC plugin for Tizen, based on GoogleWebRTC.
repository: https://github.com/flutter-tizen/plugins/tree/master/packages/flutter_webrtc
version: 0.2.2
version: 0.2.3

environment:
sdk: ">=3.3.0 <4.0.0"
Expand Down
3 changes: 1 addition & 2 deletions packages/flutter_webrtc/tizen/src/flutter_webrtc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,7 @@ void FlutterWebRTC::HandleMethodCall(
const std::string peerConnectionId = findString(params, "peerConnectionId");
RTCPeerConnection* pc = PeerConnectionForId(peerConnectionId);
if (pc == nullptr) {
result->Error("peerConnectionCloseFailed",
"peerConnectionClose() peerConnection is null");
result->Success();
return;
}
RTCPeerConnectionClose(pc, peerConnectionId, std::move(result));
Expand Down
Loading