Skip to content
This repository was archived by the owner on Aug 28, 2025. It is now read-only.
Merged
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
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.android.tools.build:gradle:7.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand Down
2 changes: 1 addition & 1 deletion example/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
1 change: 1 addition & 0 deletions example/lib/subscriber_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class _SubscriberWidgetState extends State<SubscriberWidget> {
_projectSourceId(null, 'audio');
_projectSourceId(null, 'video');
}

setState(() {});
}));

Expand Down
8 changes: 6 additions & 2 deletions example/lib/viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ Future buildSubscriber(RTCVideoRenderer localRenderer) async {
mediaElement: localRenderer);

view.on(webRTCEvents['track'], view, (ev, context) {
localRenderer.srcObject = ev.eventData as MediaStream?;
RTCTrackEvent track = ev.eventData as RTCTrackEvent;

if (track.streams.isNotEmpty) {
localRenderer.srcObject = track.streams[0];
}
});

// Based on broadcast events control different(multisource,simulcast) flows with flags and events in the ui
Expand Down Expand Up @@ -130,7 +134,7 @@ Future viewConnect(View view) async {
/// Start connection to publisher
try {
await view.connect(options: {
'events': ['active', 'inactive', 'layers', 'viewercount']
'events': ['active', 'inactive', 'layers', 'viewercount'],
});

view.webRTCPeer.initStats();
Expand Down
4 changes: 2 additions & 2 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ packages:
name: flutter_webrtc
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.7"
version: "0.8.9"
http:
dependency: "direct main"
description:
Expand Down Expand Up @@ -538,7 +538,7 @@ packages:
name: webrtc_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
version: "1.0.5"
win32:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
flutter_webrtc: ^0.8.1
flutter_webrtc: ^0.8.9
http: ^0.13.4
web_socket_channel: ^2.1.0
flutter_dotenv: ^5.0.2
Expand Down
75 changes: 49 additions & 26 deletions lib/src/peer_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class PeerConnection extends EventEmitter {
RTCSessionDescription? sessionDescription;
RTCPeerConnection? peer;
PeerConnectionStats? peerConnectionStats;
List<RTCRtpTransceiverCompleter> pendingTransceivers = [];

PeerConnection() : super();

Expand Down Expand Up @@ -230,22 +231,19 @@ class PeerConnection extends EventEmitter {
/// [streams] - Streams the track will belong to.
/// [Future] that will be resolved when the [RTCRtpTransceiver]
/// is assigned an mid value.
addRemoteTrack(media, List<MediaStream> streams) async {
Completer completer = Completer();
var transceiverCompleter = RTCRtpTransceiverCompleter(completer);
Future<RTCRtpTransceiver> addRemoteTrack(
media, List<MediaStream> streams) async {
try {
for (var stream in streams) {
stream.getTracks().forEach((track) async {
transceiverCompleter.transceiver = await peer!.addTransceiver(
track: track,
kind: media,
init: RTCRtpTransceiverInit(
direction: TransceiverDirection.RecvOnly));
stream.addTrack(transceiverCompleter.transceiver!.receiver.track!);
transceiverCompleter.completer = completer;
return completer.future;
});
}
RTCRtpTransceiver transceiverLocal = await peer!.addTransceiver(
kind: media,
init: RTCRtpTransceiverInit(
direction: TransceiverDirection.RecvOnly, streams: streams));

RTCRtpTransceiverCompleter completer = RTCRtpTransceiverCompleter();
Future<RTCRtpTransceiver> t =
completer.createTransceiver(transceiverLocal);
pendingTransceivers.add(completer);
return t;
} catch (e) {
throw Exception(e);
}
Expand Down Expand Up @@ -275,7 +273,7 @@ class PeerConnection extends EventEmitter {
(await peer!.getRemoteDescription())?.sdp, bitrate);
await setRTCRemoteSDP(sdp);
_logger.i(
'Bitrate restirctions updated: ${bitrate > 0 ? bitrate : 'unlimited'} kbps');
'Bitrate restrictions updated: ${bitrate > 0 ? bitrate : 'unlimited'} kbps');
}

String? getRTCPeerStatus() {
Expand All @@ -284,7 +282,7 @@ class PeerConnection extends EventEmitter {
return null;
}
String connectionState = getConnectionState(peer!);
_logger.i('RTC peer status getted, value: $connectionState');
_logger.i('RTC peer status got, value: $connectionState');
return connectionState;
}

Expand Down Expand Up @@ -439,11 +437,15 @@ class PeerConnection extends EventEmitter {
_logger.d('Track event value: $event');

// Listen for remote tracks events for resolving pending addRemoteTrack calls.
// TO DO
// if (event.transceiver != null) {}
// ;
if (event.transceiver != null && event.streams.isEmpty) {
if (pendingTransceivers.isNotEmpty) {
RTCRtpTransceiverCompleter transceiverCompleter =
pendingTransceivers.last;
transceiverCompleter.completeTransceiver(event.transceiver!);
}
}

instanceClass.emit(webRTCEvents['track'], this, event.streams[0]);
instanceClass.emit(webRTCEvents['track'], this, event);
};
if (peer.connectionState != null) {
peer.onConnectionState = (event) {
Expand All @@ -460,8 +462,20 @@ class PeerConnection extends EventEmitter {
};
}

// No renegotationNeeded
peer.onRenegotiationNeeded = () async {};
peer.onRenegotiationNeeded = () async {
RTCSessionDescription? remoteSdp = await peer.getRemoteDescription();
if (remoteSdp == null) {
return;
}
_logger.i('Peer onnegotiationneeded, updating local description');
RTCSessionDescription offer = await peer.createOffer();
_logger.i('Peer onnegotiationneeded, got local offer', offer.sdp);
await peer.setLocalDescription(offer);
String? sdp = SdpParser.renegotiate(offer.sdp, remoteSdp.sdp);
_logger.i('Peer onnegotiationneeded, updating remote description', sdp);
await peer.setRemoteDescription(RTCSessionDescription(sdp, 'answer'));
_logger.i('Peer onnegotiationneeded, renegotiation done');
};
}

void addMediaStreamToPeer(RTCPeerConnection? peer, MediaStream? mediaStream,
Expand Down Expand Up @@ -565,7 +579,16 @@ class PeerConnection extends EventEmitter {
}

class RTCRtpTransceiverCompleter {
Completer completer;
RTCRtpTransceiver? transceiver;
RTCRtpTransceiverCompleter(this.completer, [this.transceiver]);
final Completer _completer = Completer<RTCRtpTransceiver>();
late RTCRtpTransceiver transceiver;

Future<RTCRtpTransceiver> createTransceiver(
RTCRtpTransceiver newTransceiver) {
transceiver = newTransceiver;
return _completer.future as Future<RTCRtpTransceiver>;
}

void completeTransceiver(RTCRtpTransceiver transceiverWithMid) {
_completer.complete(transceiverWithMid);
}
}
1 change: 0 additions & 1 deletion lib/src/signaling.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:millicast_flutter_sdk/src/utils/sdp_parser.dart';
import 'utils/transaction_manager.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'logger.dart';
import './utils/sdp_parser.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';

var _logger = getLogger('Signaling');
Expand Down
50 changes: 25 additions & 25 deletions lib/src/utils/sdp_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -333,40 +333,40 @@ class SdpParser {
String? localDescription, String? remoteDescription) {
if (localDescription != null && remoteDescription != null) {
Map<String, dynamic> offer = parse(localDescription);
Map<String, dynamic> answer = parse(remoteDescription);
List<Map<String, dynamic>>? answeredMedias = [];
Map<String, dynamic> answerRemote = parse(remoteDescription);

// Check all transceivers on the offer are on the answer
for (var offeredMedia in offer['media']) {
Map<String, dynamic> answer = parse(remoteDescription);

// Get associated mid on the answer
(answer['media'] as List<dynamic>)
.removeWhere((element) => element['mid'] == offeredMedia['mid']);
bool isMidOnAnswer = (answer['media'] as List<dynamic>)
.any((answerMedia) => answerMedia['mid'] == offeredMedia['mid']);

Map<String, dynamic>? answeredMedia = answer['media'][0];
// If not found in answer
if (answeredMedia == {}) {
int? type = getMediaId(answer['media'], offeredMedia['type']);
// Set direction
if (type != null) {
answeredMedia = answer['media'][type];
answeredMedia?['direction'] =
reverseDirection(offeredMedia['direction']);
// Find first media line for same kind
Map<String, dynamic>? first = answer['media'][type];
// If found
if (first != null) {
// Copy codec info
answeredMedia?['rtp'] = first['rtp'] as List;
// Copy extension info
(answeredMedia?['ext'] as List).addAll(first['ext'] as List);
}
if (!isMidOnAnswer) {
// Find first media line for same kind
var first = (answer['media'] as List<dynamic>).firstWhere(
(answerMedia) => answerMedia['type'] == offeredMedia['type']);

// If found
if (!first.isEmpty) {
// Set mid
first['mid'] = offeredMedia['mid'];

// Set direction
first['direction'] = reverseDirection(offeredMedia['direction']);
first['ssrcs'] = null;
first['ssrcGroups'] = null;
first['msid'] = null;
answerRemote['media'].add(first);
}
//Add correct bundle
answerRemote['groups'][0]['mids'] = offer['groups'][0]['mids'];
answerRemote['origin']['sessionId'] += 1;
}
// Add it to answer
answeredMedias.add(answeredMedia!);
answer['media'] = answeredMedias;
}
remoteDescription = write(answer, null);
remoteDescription = write(answerRemote, null);
return remoteDescription;
} else {
return localDescription;
Expand Down
14 changes: 11 additions & 3 deletions lib/src/view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ class View extends BaseWebRTC {
logger: _logger) {
if (mediaElement != null) {
webRTCPeer.on(webRTCEvents['track'], this, (ev, context) {
mediaElement.srcObject = ev.eventData as MediaStream?;
RTCTrackEvent track = ev.eventData as RTCTrackEvent;
if (track.streams.isNotEmpty) {
mediaElement.srcObject = track.streams[0];
}
});
}
}
Expand Down Expand Up @@ -134,9 +137,14 @@ class View extends BaseWebRTC {
/// RTCRtpTransceiver is assigned an mid value.

Future<RTCRtpTransceiver> addRemoteTrack(
String media, List<MediaStream> streams) async {
RTCRtpMediaType media, List<MediaStream> streams) async {
_logger.i('Viewer adding remote track $media');
return webRTCPeer.addRemoteTrack(media, streams);
RTCRtpTransceiver transceiverLocal =
await webRTCPeer.addRemoteTrack(media, streams);
for (var stream in streams) {
stream.addTrack(transceiverLocal.receiver.track!);
}
return transceiverLocal;
}

/// Start projecting source in selected media ids.
Expand Down
14 changes: 7 additions & 7 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ packages:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "8.3.0"
version: "8.3.2"
characters:
dependency: transitive
description:
Expand Down Expand Up @@ -147,7 +147,7 @@ packages:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.0.2"
coverage:
dependency: transitive
description:
Expand All @@ -168,7 +168,7 @@ packages:
name: csslib
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.1"
version: "0.17.2"
dart_style:
dependency: transitive
description:
Expand Down Expand Up @@ -255,7 +255,7 @@ packages:
name: flutter_webrtc
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.7"
version: "0.8.9"
frontend_server_client:
dependency: transitive
description:
Expand Down Expand Up @@ -444,7 +444,7 @@ packages:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.6"
version: "2.1.7"
path_provider_macos:
dependency: transitive
description:
Expand All @@ -465,7 +465,7 @@ packages:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
version: "2.0.7"
platform:
dependency: transitive
description:
Expand Down Expand Up @@ -687,7 +687,7 @@ packages:
name: webrtc_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
version: "1.0.5"
win32:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies:
flutter:
sdk: flutter
logger: ^1.1.0
flutter_webrtc: ^0.8.1
flutter_webrtc: ^0.8.9
http: ^0.13.4
web_socket_channel: ^2.1.0
jwt_decode: ^0.3.1
Expand Down