From 1095e25113d53a6b7b5f890e0a6e7e48350c7292 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 11 Nov 2021 00:20:32 +0900 Subject: [PATCH] Simpler APIs (#28) * TrackSource * remove absolute import * typo * get track * set source enabled * fix symbol collision * move to participant * update proto * screen share audio * typo * format * change screen share name --- lib/src/extensions.dart | 22 +++++++++++++ lib/src/participant/local_participant.dart | 36 +++++++++++++++++++++ lib/src/participant/participant.dart | 36 +++++++++++++++++++++ lib/src/participant/remote_participant.dart | 8 ++--- lib/src/proto/livekit_models.pbenum.dart | 6 ++++ lib/src/proto/livekit_models.pbjson.dart | 3 +- lib/src/rtc_engine.dart | 2 ++ lib/src/signal_client.dart | 2 ++ lib/src/track/audio_track.dart | 4 ++- lib/src/track/local_audio_track.dart | 11 +++++-- lib/src/track/local_video_track.dart | 6 +++- lib/src/track/remote_audio_track.dart | 4 ++- lib/src/track/remote_track_publication.dart | 4 +-- lib/src/track/remote_video_track.dart | 7 ++-- lib/src/track/track.dart | 5 ++- lib/src/track/track_publication.dart | 5 ++- lib/src/track/video_track.dart | 6 ++-- lib/src/types.dart | 8 +++++ lib/src/widget/video_track_renderer.dart | 3 +- 19 files changed, 159 insertions(+), 19 deletions(-) diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart index dbf6adf..a285ff3 100644 --- a/lib/src/extensions.dart +++ b/lib/src/extensions.dart @@ -102,3 +102,25 @@ extension ConnectionQualityExt on lk_models.ConnectionQuality { }[this] ?? ConnectionQuality.unknown; } + +extension PBTrackSourceExt on lk_models.TrackSource { + TrackSource toLKType() => + { + lk_models.TrackSource.CAMERA: TrackSource.camera, + lk_models.TrackSource.MICROPHONE: TrackSource.microphone, + lk_models.TrackSource.SCREEN_SHARE: TrackSource.screenShareVideo, + lk_models.TrackSource.SCREEN_SHARE_AUDIO: TrackSource.screenShareAudio, + }[this] ?? + TrackSource.unknown; +} + +extension LKTrackSourceExt on TrackSource { + lk_models.TrackSource toPBType() => + { + TrackSource.camera: lk_models.TrackSource.CAMERA, + TrackSource.microphone: lk_models.TrackSource.MICROPHONE, + TrackSource.screenShareVideo: lk_models.TrackSource.SCREEN_SHARE, + TrackSource.screenShareAudio: lk_models.TrackSource.SCREEN_SHARE_AUDIO, + }[this] ?? + lk_models.TrackSource.UNKNOWN; +} diff --git a/lib/src/participant/local_participant.dart b/lib/src/participant/local_participant.dart index 06d8d2d..3d57341 100644 --- a/lib/src/participant/local_participant.dart +++ b/lib/src/participant/local_participant.dart @@ -58,6 +58,7 @@ class LocalParticipant extends Participant { cid: track.getCid(), name: track.name, kind: track.kind, + source: track.source.toPBType(), dtx: options?.dtx, ); @@ -102,6 +103,7 @@ class LocalParticipant extends Participant { cid: track.getCid(), name: track.name, kind: track.kind, + source: track.source.toPBType(), ); logger.fine('publishVideoTrack addTrack response: ${trackInfo}'); @@ -230,3 +232,37 @@ class LocalParticipant extends Participant { super.updateFromInfo(info); } } + +extension LocalParticipantTrackSourceExt on LocalParticipant { + Future setCameraEnabled(bool enabled) async { + return setSourceEnabled(TrackSource.camera, enabled); + } + + Future setMicrophoneEnabled(bool enabled) async { + return setSourceEnabled(TrackSource.microphone, enabled); + } + + Future setSourceEnabled(TrackSource source, bool enabled) async { + final pub = getTrackPublicationBySource(source); + if (pub != null) { + if (enabled) { + pub.muted = false; + } else { + if (source == TrackSource.screenShareVideo) { + await unpublishTrack(pub.sid); + } else { + pub.muted = true; + } + } + } else if (enabled) { + if (source == TrackSource.camera) { + final track = await LocalVideoTrack.createCameraTrack(); + await publishVideoTrack(track); + } else if (source == TrackSource.microphone) { + final track = await LocalAudioTrack.create(); + await publishAudioTrack(track); + } + // TODO: Screen share + } + } +} diff --git a/lib/src/participant/participant.dart b/lib/src/participant/participant.dart index 8afdee4..132588f 100644 --- a/lib/src/participant/participant.dart +++ b/lib/src/participant/participant.dart @@ -1,4 +1,5 @@ import 'package:collection/collection.dart'; +import 'package:livekit_client/src/track/track.dart'; import 'package:meta/meta.dart'; import '../events.dart'; @@ -187,3 +188,38 @@ extension ParticipantExt on Participant { .where((e) => e.kind == lk_models.TrackType.AUDIO) .toList(); } + +extension ParticipantTrackSourceExt on Participant { + bool isCameraEnabled() { + return !(getTrackPublicationBySource(TrackSource.camera)?.muted ?? true); + } + + bool isMicrophoneEnabled() { + return !(getTrackPublicationBySource(TrackSource.microphone)?.muted ?? + true); + } + + /// Find a track publication by its [TrackSource] + TrackPublication? getTrackPublicationBySource(TrackSource source) { + if (source == TrackSource.unknown) return null; + // try to find by source + final result = + trackPublications.values.firstWhereOrNull((e) => e.source == source); + if (result != null) return result; + // try to find by compatibility + return trackPublications.values + .where((e) => e.source == TrackSource.unknown) + .firstWhereOrNull((e) => + (source == TrackSource.microphone && + e.kind == lk_models.TrackType.AUDIO) || + (source == TrackSource.camera && + e.kind == lk_models.TrackType.VIDEO && + e.name != Track.screenShareName) || + (source == TrackSource.screenShareVideo && + e.kind == lk_models.TrackType.VIDEO && + e.name == Track.screenShareName) || + (source == TrackSource.screenShareAudio && + e.kind == lk_models.TrackType.AUDIO && + e.name == Track.screenShareName)); + } +} diff --git a/lib/src/participant/remote_participant.dart b/lib/src/participant/remote_participant.dart index 9f60e9d..2397697 100644 --- a/lib/src/participant/remote_participant.dart +++ b/lib/src/participant/remote_participant.dart @@ -1,6 +1,4 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; -import 'package:livekit_client/src/track/remote_audio_track.dart'; -import 'package:livekit_client/src/track/remote_video_track.dart'; import 'package:meta/meta.dart'; import '../constants.dart'; @@ -10,7 +8,9 @@ import '../logger.dart'; import '../managers/event.dart'; import '../proto/livekit_models.pb.dart' as lk_models; import '../rtc_engine.dart'; +import '../track/remote_audio_track.dart'; import '../track/remote_track_publication.dart'; +import '../track/remote_video_track.dart'; import '../track/track.dart'; import '../types.dart'; import 'participant.dart'; @@ -91,10 +91,10 @@ class RemoteParticipant extends Participant { final Track track; if (pub.kind == lk_models.TrackType.AUDIO) { // audio track - track = RemoteAudioTrack(pub.name, mediaTrack, stream); + track = RemoteAudioTrack(pub.source, pub.name, mediaTrack, stream); } else { // video track - track = RemoteVideoTrack(pub.name, mediaTrack, stream); + track = RemoteVideoTrack(pub.source, pub.name, mediaTrack, stream); } await track.start(); diff --git a/lib/src/proto/livekit_models.pbenum.dart b/lib/src/proto/livekit_models.pbenum.dart index e9f7034..1e5f124 100644 --- a/lib/src/proto/livekit_models.pbenum.dart +++ b/lib/src/proto/livekit_models.pbenum.dart @@ -60,12 +60,18 @@ class TrackSource extends $pb.ProtobufEnum { const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SCREEN_SHARE'); + static const TrackSource SCREEN_SHARE_AUDIO = TrackSource._( + 4, + const $core.bool.fromEnvironment('protobuf.omit_enum_names') + ? '' + : 'SCREEN_SHARE_AUDIO'); static const $core.List values = [ UNKNOWN, CAMERA, MICROPHONE, SCREEN_SHARE, + SCREEN_SHARE_AUDIO, ]; static final $core.Map<$core.int, TrackSource> _byValue = diff --git a/lib/src/proto/livekit_models.pbjson.dart b/lib/src/proto/livekit_models.pbjson.dart index 9b3a018..95c7b86 100644 --- a/lib/src/proto/livekit_models.pbjson.dart +++ b/lib/src/proto/livekit_models.pbjson.dart @@ -30,12 +30,13 @@ const TrackSource$json = const { const {'1': 'CAMERA', '2': 1}, const {'1': 'MICROPHONE', '2': 2}, const {'1': 'SCREEN_SHARE', '2': 3}, + const {'1': 'SCREEN_SHARE_AUDIO', '2': 4}, ], }; /// Descriptor for `TrackSource`. Decode as a `google.protobuf.EnumDescriptorProto`. final $typed_data.Uint8List trackSourceDescriptor = $convert.base64Decode( - 'CgtUcmFja1NvdXJjZRILCgdVTktOT1dOEAASCgoGQ0FNRVJBEAESDgoKTUlDUk9QSE9ORRACEhAKDFNDUkVFTl9TSEFSRRAD'); + 'CgtUcmFja1NvdXJjZRILCgdVTktOT1dOEAASCgoGQ0FNRVJBEAESDgoKTUlDUk9QSE9ORRACEhAKDFNDUkVFTl9TSEFSRRADEhYKElNDUkVFTl9TSEFSRV9BVURJTxAE'); @$core.Deprecated('Use connectionQualityDescriptor instead') const ConnectionQuality$json = const { '1': 'ConnectionQuality', diff --git a/lib/src/rtc_engine.dart b/lib/src/rtc_engine.dart index ef37299..49dd95e 100644 --- a/lib/src/rtc_engine.dart +++ b/lib/src/rtc_engine.dart @@ -145,6 +145,7 @@ class RTCEngine extends Disposable with EventsEmittable { required String cid, required String name, required lk_models.TrackType kind, + required lk_models.TrackSource source, TrackDimension? dimension, bool? dtx, }) async { @@ -155,6 +156,7 @@ class RTCEngine extends Disposable with EventsEmittable { cid: cid, name: name, type: kind, + source: source, dimension: dimension, dtx: dtx, ); diff --git a/lib/src/signal_client.dart b/lib/src/signal_client.dart index 9b9f437..ccd5c5e 100644 --- a/lib/src/signal_client.dart +++ b/lib/src/signal_client.dart @@ -151,6 +151,7 @@ class SignalClient extends Disposable with EventsEmittable { required String cid, required String name, required lk_models.TrackType type, + required lk_models.TrackSource source, TrackDimension? dimension, bool? dtx, }) { @@ -158,6 +159,7 @@ class SignalClient extends Disposable with EventsEmittable { cid: cid, name: name, type: type, + source: source, ); if (type == lk_models.TrackType.VIDEO && dimension != null) { diff --git a/lib/src/track/audio_track.dart b/lib/src/track/audio_track.dart index 2a7ac9d..31a09bc 100644 --- a/lib/src/track/audio_track.dart +++ b/lib/src/track/audio_track.dart @@ -8,7 +8,7 @@ import 'package:synchronized/synchronized.dart' as sync; import '../logger.dart'; import '../proto/livekit_models.pb.dart' as lk_models; import '../support/native_audio.dart'; - +import '../types.dart'; import 'local_audio_track.dart'; import 'track.dart'; @@ -35,11 +35,13 @@ abstract class AudioTrack extends Track { rtc.MediaStream? mediaStream; AudioTrack( + TrackSource source, String name, rtc.MediaStreamTrack track, this.mediaStream, ) : super( lk_models.TrackType.AUDIO, + source, name, track, ); diff --git a/lib/src/track/local_audio_track.dart b/lib/src/track/local_audio_track.dart index 9b0709f..aa3beca 100644 --- a/lib/src/track/local_audio_track.dart +++ b/lib/src/track/local_audio_track.dart @@ -3,16 +3,18 @@ import 'dart:async'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import '../exceptions.dart'; +import '../types.dart'; import 'audio_track.dart'; import 'options.dart'; class LocalAudioTrack extends AudioTrack { // private constructor LocalAudioTrack._( + TrackSource source, String name, rtc.MediaStreamTrack track, rtc.MediaStream stream, - ) : super(name, track, stream); + ) : super(source, name, track, stream); /// Creates a new audio track from the default audio input device. static Future create( @@ -30,7 +32,12 @@ class LocalAudioTrack extends AudioTrack { if (stream.getAudioTracks().isEmpty) throw TrackCreateException(); - return LocalAudioTrack._('', stream.getAudioTracks().first, stream); + return LocalAudioTrack._( + TrackSource.microphone, + '', + stream.getAudioTracks().first, + stream, + ); } @override diff --git a/lib/src/track/local_video_track.dart b/lib/src/track/local_video_track.dart index c83e55b..99fc1e7 100644 --- a/lib/src/track/local_video_track.dart +++ b/lib/src/track/local_video_track.dart @@ -2,6 +2,7 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import '../exceptions.dart'; import '../logger.dart'; +import '../types.dart'; import 'options.dart'; import 'track.dart'; import 'video_track.dart'; @@ -18,11 +19,12 @@ class LocalVideoTrack extends VideoTrack { // Private constructor // LocalVideoTrack._( + TrackSource source, String name, rtc.MediaStreamTrack mediaTrack, rtc.MediaStream stream, this.currentOptions, - ) : super(name, mediaTrack, stream); + ) : super(source, name, mediaTrack, stream); rtc.RTCRtpSender? get sender => transceiver?.sender; @@ -61,6 +63,7 @@ class LocalVideoTrack extends VideoTrack { options ??= const CameraTrackOptions(); final stream = await _createStream(options); return LocalVideoTrack._( + TrackSource.camera, Track.cameraName, stream.getVideoTracks().first, stream, @@ -74,6 +77,7 @@ class LocalVideoTrack extends VideoTrack { options ??= const ScreenTrackOptions(); final stream = await _createStream(options); return LocalVideoTrack._( + TrackSource.screenShareVideo, Track.screenShareName, stream.getVideoTracks().first, stream, diff --git a/lib/src/track/remote_audio_track.dart b/lib/src/track/remote_audio_track.dart index cf15310..03c0714 100644 --- a/lib/src/track/remote_audio_track.dart +++ b/lib/src/track/remote_audio_track.dart @@ -1,14 +1,16 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; +import '../types.dart'; import '_audio_api.dart' if (dart.library.html) '_audio_html.dart' as audio; import 'audio_track.dart'; class RemoteAudioTrack extends AudioTrack { // RemoteAudioTrack( + TrackSource source, String name, rtc.MediaStreamTrack track, rtc.MediaStream stream, - ) : super(name, track, stream); + ) : super(source, name, track, stream); @override Future start() async { diff --git a/lib/src/track/remote_track_publication.dart b/lib/src/track/remote_track_publication.dart index b09dd4a..7b725b0 100644 --- a/lib/src/track/remote_track_publication.dart +++ b/lib/src/track/remote_track_publication.dart @@ -2,12 +2,12 @@ import 'dart:math'; import 'dart:ui'; import 'package:collection/collection.dart'; -import 'package:livekit_client/src/internal/events.dart'; -import 'package:livekit_client/src/logger.dart'; import 'package:meta/meta.dart'; import '../events.dart'; import '../extensions.dart'; +import '../internal/events.dart'; +import '../logger.dart'; import '../participant/remote_participant.dart'; import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_rtc.pb.dart' as lk_rtc; diff --git a/lib/src/track/remote_video_track.dart b/lib/src/track/remote_video_track.dart index e976ef2..0e11c83 100644 --- a/lib/src/track/remote_video_track.dart +++ b/lib/src/track/remote_video_track.dart @@ -1,13 +1,16 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; -import 'package:livekit_client/src/track/video_track.dart'; + +import '../track/video_track.dart'; +import '../types.dart'; class RemoteVideoTrack extends VideoTrack { // RemoteVideoTrack( + TrackSource source, String name, rtc.MediaStreamTrack mediaTrack, rtc.MediaStream stream, - ) : super(name, mediaTrack, stream); + ) : super(source, name, mediaTrack, stream); @override Future start() async { diff --git a/lib/src/track/track.dart b/lib/src/track/track.dart index 449267f..2cb8c29 100644 --- a/lib/src/track/track.dart +++ b/lib/src/track/track.dart @@ -9,6 +9,7 @@ import '../logger.dart'; import '../managers/event.dart'; import '../proto/livekit_models.pb.dart' as lk_models; import '../support/disposable.dart'; +import '../types.dart'; /// Wrapper around a MediaStreamTrack with additional metadata. /// Base for [AudioTrack] and [VideoTrack], @@ -17,10 +18,11 @@ abstract class Track extends DisposableChangeNotifier with EventsEmittable { static const uuid = Uuid(); static const cameraName = 'camera'; - static const screenShareName = 'screen'; + static const screenShareName = 'screenshare'; final String name; final lk_models.TrackType kind; + final TrackSource source; rtc.MediaStreamTrack mediaStreamTrack; String? sid; @@ -33,6 +35,7 @@ abstract class Track extends DisposableChangeNotifier Track( this.kind, + this.source, this.name, this.mediaStreamTrack, ) { diff --git a/lib/src/track/track_publication.dart b/lib/src/track/track_publication.dart index a97326b..bbf2b4a 100644 --- a/lib/src/track/track_publication.dart +++ b/lib/src/track/track_publication.dart @@ -1,6 +1,7 @@ import '../support/disposable.dart'; import '../proto/livekit_models.pb.dart' as lk_models; import '../types.dart'; +import '../extensions.dart'; import 'track.dart'; /// Represents a track that's published to the server. This class contains @@ -13,6 +14,7 @@ abstract class TrackPublication extends Disposable { final String sid; final String name; final lk_models.TrackType kind; + final TrackSource source; Track? track; bool muted = false; @@ -24,7 +26,8 @@ abstract class TrackPublication extends Disposable { TrackPublication.fromInfo(lk_models.TrackInfo info) : sid = info.sid, name = info.name, - kind = info.type { + kind = info.type, + source = info.source.toLKType() { updateFromInfo(info); } diff --git a/lib/src/track/video_track.dart b/lib/src/track/video_track.dart index 7f28468..8a927f5 100644 --- a/lib/src/track/video_track.dart +++ b/lib/src/track/video_track.dart @@ -1,8 +1,9 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; -import 'package:livekit_client/src/internal/events.dart'; import 'package:meta/meta.dart'; +import '../internal/events.dart'; import '../proto/livekit_models.pb.dart' as lk_models; +import '../types.dart'; import 'track.dart'; /// A video track will notify when its mediaTrack has changed. @@ -10,12 +11,13 @@ abstract class VideoTrack extends Track { rtc.MediaStream _mediaStream; VideoTrack( + TrackSource source, String name, rtc.MediaStreamTrack mediaTrack, this._mediaStream, - // this._client, ) : super( lk_models.TrackType.VIDEO, + source, name, mediaTrack, ); diff --git a/lib/src/types.dart b/lib/src/types.dart index a092ec1..bae59b9 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -29,6 +29,14 @@ enum Reliability { lossy, } +enum TrackSource { + unknown, + camera, + microphone, + screenShareVideo, + screenShareAudio, +} + enum CloseReason { network, // ... diff --git a/lib/src/widget/video_track_renderer.dart b/lib/src/widget/video_track_renderer.dart index 370ecac..e824c4c 100644 --- a/lib/src/widget/video_track_renderer.dart +++ b/lib/src/widget/video_track_renderer.dart @@ -1,12 +1,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; -import 'package:livekit_client/livekit_client.dart'; import 'package:visibility_detector/visibility_detector.dart'; +import '../events.dart'; import '../extensions.dart'; import '../internal/events.dart'; import '../logger.dart'; +import '../managers/event.dart'; import '../track/local_video_track.dart'; import '../track/video_track.dart';