Better audio management (#10)

* add audio_session package

* `AudioManager` initial design

* try to integrate audio manager

* configure audio session

* `DisposeAware`

disposing if already published causes exception.

* organize

* guard flutter_webrtc calls

* websocket async fix

* use `ConnectionState` instead of `_isClosed` and `isReconnecting`

* change create audio track defaults

* update protos

* protocol 3 speaker updates

* fix exception

* emit `RoomDisconnectedEvent` only once

* fix exception

* keep track of local / remote audio tracks

* explicit types

* manage track state

* re-structure audio management

* change defaults

* unpublish all

* use experimental build

* change defaults

* configuring is optional

* call native `RTCAudioSession.setConfiguration`

* defaults adjustment

* iOS only for now

* use lib 92.4515.07

* clean up

* revert audio options for now

* remove pod source

* rename apple related audio

* `createListener` method

* organize native audio

* `SpeakingChangedEvent` only on `Participant`

* refactoring

* fix ios compile

* fix configure audio only for iOS logic

* minor fix & clean up

* change dispose logic

* format protos

* fix unpublishTrack

* `createListener` into a mixin

* simplify

* use flutter_webrtc master

* unpublish for example

* update ios icon

* android icon

* web icon

* favicon
This commit is contained in:
Hiroshi Horie
2021-10-01 02:47:18 +09:00
committed by GitHub
parent ec17907cdc
commit 001f688729
101 changed files with 1658 additions and 681 deletions
+53 -35
View File
@@ -1,8 +1,9 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:meta/meta.dart';
import '../errors.dart';
import '../events.dart';
import '../exceptions.dart';
import '../extensions.dart';
import '../logger.dart';
import '../managers/event.dart';
@@ -19,16 +20,17 @@ import 'participant.dart';
/// Represents the current participant in the room.
class LocalParticipant extends Participant {
final RTCEngine _engine;
@internal
final RTCEngine engine;
@internal
final TrackPublishOptions? defaultPublishOptions;
LocalParticipant({
required RTCEngine engine,
required this.engine,
required lk_models.ParticipantInfo info,
this.defaultPublishOptions,
required EventsEmitter<RoomEvent> roomEvents,
}) : _engine = engine,
super(
}) : super(
info.sid,
info.identity,
roomEvents: roomEvents,
@@ -36,39 +38,43 @@ class LocalParticipant extends Participant {
updateFromInfo(info);
}
/// for internal use
/// {@nodoc}
RTCEngine get engine => _engine;
/// publish an audio track to the room
Future<TrackPublication> publishAudioTrack(LocalAudioTrack track) async {
if (audioTracks.any((e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) {
throw TrackPublishException('track already exists');
}
// try {
final trackInfo = await _engine.addTrack(
cid: track.getCid(),
name: track.name,
kind: track.kind,
);
// await AudioManager().incrementPublishCounter();
final transceiverInit = rtc.RTCRtpTransceiverInit(
direction: rtc.TransceiverDirection.SendOnly,
);
// addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web)
track.transceiver = await _engine.publisher?.pc.addTransceiver(
track: track.mediaStreamTrack,
kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeAudio,
init: transceiverInit,
);
await _engine.negotiate();
try {
final trackInfo = await engine.addTrack(
cid: track.getCid(),
name: track.name,
kind: track.kind,
);
final pub = LocalTrackPublication(trackInfo, track, this);
addTrackPublication(pub);
notifyListeners();
await track.start();
return pub;
final transceiverInit = rtc.RTCRtpTransceiverInit(
direction: rtc.TransceiverDirection.SendOnly,
);
// addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web)
track.transceiver = await engine.publisher?.pc.addTransceiver(
track: track.mediaStreamTrack,
kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeAudio,
init: transceiverInit,
);
await engine.negotiate();
final pub = LocalTrackPublication(trackInfo, track, this);
addTrackPublication(pub);
notifyListeners();
return pub;
} catch (e) {
// In any case there was an exception, revert the count.
// await AudioManager().decrementPublishCounter();
rethrow;
}
}
/// Publish a video track to the room
@@ -83,13 +89,16 @@ class LocalParticipant extends Participant {
// Use default options from `ConnectOptions` if options is null
options = options ?? defaultPublishOptions;
final trackInfo = await _engine.addTrack(
final trackInfo = await engine.addTrack(
cid: track.getCid(),
name: track.name,
kind: track.kind,
);
logger.fine('publishVideoTrack addTrack response: ${trackInfo}');
await track.start();
// Video encodings and simulcasts
// use constraints passed to getUserMedia by default
@@ -124,14 +133,14 @@ class LocalParticipant extends Participant {
streams: [track.mediaStream],
);
logger.fine('publishVideoTrack publisher: ${_engine.publisher}');
logger.fine('publishVideoTrack publisher: ${engine.publisher}');
track.transceiver = await _engine.publisher?.pc.addTransceiver(
track.transceiver = await engine.publisher?.pc.addTransceiver(
track: track.mediaStreamTrack,
kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeVideo,
init: transceiverInit,
);
await _engine.negotiate();
await engine.negotiate();
final pub = LocalTrackPublication(trackInfo, track, this);
addTrackPublication(pub);
@@ -156,8 +165,17 @@ class LocalParticipant extends Participant {
final sender = track.transceiver?.sender;
if (sender != null) {
await engine.publisher?.pc.removeTrack(sender);
await engine.negotiate();
try {
await engine.publisher?.pc.removeTrack(sender);
} catch (_) {
logger.warning('[$objectId] rtc.removeTrack() did throw ${_}');
}
// doesn't make sense to negotiate if already disposed
if (!isDisposed) {
// manual negotiation since track changed
await engine.negotiate();
}
}
}
}
+13 -16
View File
@@ -1,13 +1,12 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:meta/meta.dart';
import '../classes/change_notifier.dart';
import '../events.dart';
import '../extensions.dart';
import '../logger.dart';
import '../managers/event.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import '../support/disposable.dart';
import '../track/track_publication.dart';
import 'remote_participant.dart';
@@ -21,7 +20,7 @@ import 'remote_participant.dart';
/// Base for [RemoteParticipant] and [LocalParticipant],
/// can not be instantiated directly.
abstract class Participant extends LKChangeNotifier {
abstract class Participant extends DisposableChangeNotifier with EventsEmittable<ParticipantEvent> {
/// map of track sid => published track
final trackPublications = <String, TrackPublication>{};
@@ -44,7 +43,6 @@ abstract class Participant extends LKChangeNotifier {
bool _isSpeaking = false;
// suppport for multiple event listeners
final events = EventsEmitter<ParticipantEvent>();
final EventsEmitter<RoomEvent> roomEvents;
/// when the participant joined the room
@@ -85,14 +83,11 @@ abstract class Participant extends LKChangeNotifier {
logger.fine('[ParticipantEvent] $event, will notifyListeners()');
notifyListeners();
});
}
@override
@mustCallSuper
Future<void> dispose() async {
logger.fine('$objectId dispose()');
await events.dispose();
super.dispose();
onDispose(() async {
await events.dispose();
await unpublishAllTracks();
});
}
/// for internal use
@@ -106,7 +101,7 @@ abstract class Participant extends LKChangeNotifier {
lastSpokeAt = DateTime.now();
}
[events, roomEvents].emit(SpeakingChangedEvent(
events.emit(SpeakingChangedEvent(
participant: this,
speaking: speaking,
));
@@ -145,15 +140,17 @@ abstract class Participant extends LKChangeNotifier {
// Must implement
Future<void> unpublishTrack(String trackSid, {bool notify = false});
Future<void> unpublishAllTracks() async {
final _ = List<TrackPublication>.from(trackPublications.values);
for (final track in _) {
await unpublishTrack(track.sid);
Future<void> unpublishAllTracks({bool notify = false}) async {
final trackSids = trackPublications.keys.toSet();
for (final trackid in trackSids) {
await unpublishTrack(trackid, notify: notify);
}
}
//
// Equality operators
// Object is considered equal when sid is equal
//
@override
int get hashCode => sid.hashCode;
+8 -3
View File
@@ -90,8 +90,10 @@ class RemoteParticipant extends Participant {
final Track track;
if (pub.kind == lk_models.TrackType.AUDIO) {
// audio track
// await AudioManager().incrementSubscriptionCounter();
final audioTrack = AudioTrack(pub.name, mediaTrack, stream);
audioTrack.start();
await audioTrack.start();
track = audioTrack;
} else {
// video track
@@ -143,8 +145,7 @@ class RemoteParticipant extends Participant {
// unpublish any track that is not in the info
final validSids = info.tracks.map((e) => e.sid);
final removeSids =
trackPublications.values.where((e) => !validSids.contains(e.sid)).map((e) => e.sid);
final removeSids = trackPublications.keys.where((e) => !validSids.contains(e)).toSet();
for (final sid in removeSids) {
await unpublishTrack(sid, notify: true);
}
@@ -165,6 +166,10 @@ class RemoteParticipant extends Participant {
track: track,
publication: pub,
));
// if (track is AudioTrack) {
// await AudioManager().decrementSubscriptionCounter();
// }
}
if (notify) {