This commit is contained in:
Hiroshi Horie
2021-11-11 00:21:17 +09:00
19 changed files with 159 additions and 19 deletions
+22
View File
@@ -102,3 +102,25 @@ extension ConnectionQualityExt on lk_models.ConnectionQuality {
}[this] ?? }[this] ??
ConnectionQuality.unknown; ConnectionQuality.unknown;
} }
extension PBTrackSourceExt on lk_models.TrackSource {
TrackSource toLKType() =>
<lk_models.TrackSource, TrackSource>{
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, lk_models.TrackSource>{
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;
}
@@ -58,6 +58,7 @@ class LocalParticipant extends Participant {
cid: track.getCid(), cid: track.getCid(),
name: track.name, name: track.name,
kind: track.kind, kind: track.kind,
source: track.source.toPBType(),
dtx: options?.dtx, dtx: options?.dtx,
); );
@@ -102,6 +103,7 @@ class LocalParticipant extends Participant {
cid: track.getCid(), cid: track.getCid(),
name: track.name, name: track.name,
kind: track.kind, kind: track.kind,
source: track.source.toPBType(),
); );
logger.fine('publishVideoTrack addTrack response: ${trackInfo}'); logger.fine('publishVideoTrack addTrack response: ${trackInfo}');
@@ -230,3 +232,37 @@ class LocalParticipant extends Participant {
super.updateFromInfo(info); super.updateFromInfo(info);
} }
} }
extension LocalParticipantTrackSourceExt on LocalParticipant {
Future<void> setCameraEnabled(bool enabled) async {
return setSourceEnabled(TrackSource.camera, enabled);
}
Future<void> setMicrophoneEnabled(bool enabled) async {
return setSourceEnabled(TrackSource.microphone, enabled);
}
Future<void> 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
}
}
}
+36
View File
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:livekit_client/src/track/track.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../events.dart'; import '../events.dart';
@@ -187,3 +188,38 @@ extension ParticipantExt on Participant {
.where((e) => e.kind == lk_models.TrackType.AUDIO) .where((e) => e.kind == lk_models.TrackType.AUDIO)
.toList(); .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));
}
}
+4 -4
View File
@@ -1,6 +1,4 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; 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 'package:meta/meta.dart';
import '../constants.dart'; import '../constants.dart';
@@ -10,7 +8,9 @@ import '../logger.dart';
import '../managers/event.dart'; import '../managers/event.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../rtc_engine.dart'; import '../rtc_engine.dart';
import '../track/remote_audio_track.dart';
import '../track/remote_track_publication.dart'; import '../track/remote_track_publication.dart';
import '../track/remote_video_track.dart';
import '../track/track.dart'; import '../track/track.dart';
import '../types.dart'; import '../types.dart';
import 'participant.dart'; import 'participant.dart';
@@ -91,10 +91,10 @@ class RemoteParticipant extends Participant {
final Track track; final Track track;
if (pub.kind == lk_models.TrackType.AUDIO) { if (pub.kind == lk_models.TrackType.AUDIO) {
// audio track // audio track
track = RemoteAudioTrack(pub.name, mediaTrack, stream); track = RemoteAudioTrack(pub.source, pub.name, mediaTrack, stream);
} else { } else {
// video track // video track
track = RemoteVideoTrack(pub.name, mediaTrack, stream); track = RemoteVideoTrack(pub.source, pub.name, mediaTrack, stream);
} }
await track.start(); await track.start();
+6
View File
@@ -60,12 +60,18 @@ class TrackSource extends $pb.ProtobufEnum {
const $core.bool.fromEnvironment('protobuf.omit_enum_names') const $core.bool.fromEnvironment('protobuf.omit_enum_names')
? '' ? ''
: 'SCREEN_SHARE'); : '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<TrackSource> values = <TrackSource>[ static const $core.List<TrackSource> values = <TrackSource>[
UNKNOWN, UNKNOWN,
CAMERA, CAMERA,
MICROPHONE, MICROPHONE,
SCREEN_SHARE, SCREEN_SHARE,
SCREEN_SHARE_AUDIO,
]; ];
static final $core.Map<$core.int, TrackSource> _byValue = static final $core.Map<$core.int, TrackSource> _byValue =
+2 -1
View File
@@ -30,12 +30,13 @@ const TrackSource$json = const {
const {'1': 'CAMERA', '2': 1}, const {'1': 'CAMERA', '2': 1},
const {'1': 'MICROPHONE', '2': 2}, const {'1': 'MICROPHONE', '2': 2},
const {'1': 'SCREEN_SHARE', '2': 3}, const {'1': 'SCREEN_SHARE', '2': 3},
const {'1': 'SCREEN_SHARE_AUDIO', '2': 4},
], ],
}; };
/// Descriptor for `TrackSource`. Decode as a `google.protobuf.EnumDescriptorProto`. /// Descriptor for `TrackSource`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List trackSourceDescriptor = $convert.base64Decode( final $typed_data.Uint8List trackSourceDescriptor = $convert.base64Decode(
'CgtUcmFja1NvdXJjZRILCgdVTktOT1dOEAASCgoGQ0FNRVJBEAESDgoKTUlDUk9QSE9ORRACEhAKDFNDUkVFTl9TSEFSRRAD'); 'CgtUcmFja1NvdXJjZRILCgdVTktOT1dOEAASCgoGQ0FNRVJBEAESDgoKTUlDUk9QSE9ORRACEhAKDFNDUkVFTl9TSEFSRRADEhYKElNDUkVFTl9TSEFSRV9BVURJTxAE');
@$core.Deprecated('Use connectionQualityDescriptor instead') @$core.Deprecated('Use connectionQualityDescriptor instead')
const ConnectionQuality$json = const { const ConnectionQuality$json = const {
'1': 'ConnectionQuality', '1': 'ConnectionQuality',
+2
View File
@@ -145,6 +145,7 @@ class RTCEngine extends Disposable with EventsEmittable<EngineEvent> {
required String cid, required String cid,
required String name, required String name,
required lk_models.TrackType kind, required lk_models.TrackType kind,
required lk_models.TrackSource source,
TrackDimension? dimension, TrackDimension? dimension,
bool? dtx, bool? dtx,
}) async { }) async {
@@ -155,6 +156,7 @@ class RTCEngine extends Disposable with EventsEmittable<EngineEvent> {
cid: cid, cid: cid,
name: name, name: name,
type: kind, type: kind,
source: source,
dimension: dimension, dimension: dimension,
dtx: dtx, dtx: dtx,
); );
+2
View File
@@ -151,6 +151,7 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
required String cid, required String cid,
required String name, required String name,
required lk_models.TrackType type, required lk_models.TrackType type,
required lk_models.TrackSource source,
TrackDimension? dimension, TrackDimension? dimension,
bool? dtx, bool? dtx,
}) { }) {
@@ -158,6 +159,7 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
cid: cid, cid: cid,
name: name, name: name,
type: type, type: type,
source: source,
); );
if (type == lk_models.TrackType.VIDEO && dimension != null) { if (type == lk_models.TrackType.VIDEO && dimension != null) {
+3 -1
View File
@@ -8,7 +8,7 @@ import 'package:synchronized/synchronized.dart' as sync;
import '../logger.dart'; import '../logger.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../support/native_audio.dart'; import '../support/native_audio.dart';
import '../types.dart';
import 'local_audio_track.dart'; import 'local_audio_track.dart';
import 'track.dart'; import 'track.dart';
@@ -35,11 +35,13 @@ abstract class AudioTrack extends Track {
rtc.MediaStream? mediaStream; rtc.MediaStream? mediaStream;
AudioTrack( AudioTrack(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack track, rtc.MediaStreamTrack track,
this.mediaStream, this.mediaStream,
) : super( ) : super(
lk_models.TrackType.AUDIO, lk_models.TrackType.AUDIO,
source,
name, name,
track, track,
); );
+9 -2
View File
@@ -4,16 +4,18 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import '../exceptions.dart'; import '../exceptions.dart';
import '../logger.dart'; import '../logger.dart';
import '../types.dart';
import 'audio_track.dart'; import 'audio_track.dart';
import 'options.dart'; import 'options.dart';
class LocalAudioTrack extends AudioTrack { class LocalAudioTrack extends AudioTrack {
// private constructor // private constructor
LocalAudioTrack._( LocalAudioTrack._(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack track, rtc.MediaStreamTrack track,
rtc.MediaStream stream, rtc.MediaStream stream,
) : super(name, track, stream); ) : super(source, name, track, stream);
/// Creates a new audio track from the default audio input device. /// Creates a new audio track from the default audio input device.
static Future<LocalAudioTrack> create( static Future<LocalAudioTrack> create(
@@ -31,7 +33,12 @@ class LocalAudioTrack extends AudioTrack {
if (stream.getAudioTracks().isEmpty) throw TrackCreateException(); if (stream.getAudioTracks().isEmpty) throw TrackCreateException();
return LocalAudioTrack._('', stream.getAudioTracks().first, stream); return LocalAudioTrack._(
TrackSource.microphone,
'',
stream.getAudioTracks().first,
stream,
);
} }
@override @override
+5 -1
View File
@@ -2,6 +2,7 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import '../exceptions.dart'; import '../exceptions.dart';
import '../logger.dart'; import '../logger.dart';
import '../types.dart';
import 'options.dart'; import 'options.dart';
import 'track.dart'; import 'track.dart';
import 'video_track.dart'; import 'video_track.dart';
@@ -18,11 +19,12 @@ class LocalVideoTrack extends VideoTrack {
// Private constructor // Private constructor
// //
LocalVideoTrack._( LocalVideoTrack._(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack mediaTrack, rtc.MediaStreamTrack mediaTrack,
rtc.MediaStream stream, rtc.MediaStream stream,
this.currentOptions, this.currentOptions,
) : super(name, mediaTrack, stream); ) : super(source, name, mediaTrack, stream);
rtc.RTCRtpSender? get sender => transceiver?.sender; rtc.RTCRtpSender? get sender => transceiver?.sender;
@@ -61,6 +63,7 @@ class LocalVideoTrack extends VideoTrack {
options ??= const CameraTrackOptions(); options ??= const CameraTrackOptions();
final stream = await _createStream(options); final stream = await _createStream(options);
return LocalVideoTrack._( return LocalVideoTrack._(
TrackSource.camera,
Track.cameraName, Track.cameraName,
stream.getVideoTracks().first, stream.getVideoTracks().first,
stream, stream,
@@ -74,6 +77,7 @@ class LocalVideoTrack extends VideoTrack {
options ??= const ScreenTrackOptions(); options ??= const ScreenTrackOptions();
final stream = await _createStream(options); final stream = await _createStream(options);
return LocalVideoTrack._( return LocalVideoTrack._(
TrackSource.screenShareVideo,
Track.screenShareName, Track.screenShareName,
stream.getVideoTracks().first, stream.getVideoTracks().first,
stream, stream,
+3 -1
View File
@@ -1,14 +1,16 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; 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_api.dart' if (dart.library.html) '_audio_html.dart' as audio;
import 'audio_track.dart'; import 'audio_track.dart';
class RemoteAudioTrack extends AudioTrack { class RemoteAudioTrack extends AudioTrack {
// //
RemoteAudioTrack( RemoteAudioTrack(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack track, rtc.MediaStreamTrack track,
rtc.MediaStream stream, rtc.MediaStream stream,
) : super(name, track, stream); ) : super(source, name, track, stream);
@override @override
Future<bool> start() async { Future<bool> start() async {
+2 -2
View File
@@ -2,12 +2,12 @@ import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:collection/collection.dart'; 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 'package:meta/meta.dart';
import '../events.dart'; import '../events.dart';
import '../extensions.dart'; import '../extensions.dart';
import '../internal/events.dart';
import '../logger.dart';
import '../participant/remote_participant.dart'; import '../participant/remote_participant.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../proto/livekit_rtc.pb.dart' as lk_rtc; import '../proto/livekit_rtc.pb.dart' as lk_rtc;
+5 -2
View File
@@ -1,13 +1,16 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; 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 { class RemoteVideoTrack extends VideoTrack {
// //
RemoteVideoTrack( RemoteVideoTrack(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack mediaTrack, rtc.MediaStreamTrack mediaTrack,
rtc.MediaStream stream, rtc.MediaStream stream,
) : super(name, mediaTrack, stream); ) : super(source, name, mediaTrack, stream);
@override @override
Future<bool> start() async { Future<bool> start() async {
+4 -1
View File
@@ -9,6 +9,7 @@ import '../logger.dart';
import '../managers/event.dart'; import '../managers/event.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../support/disposable.dart'; import '../support/disposable.dart';
import '../types.dart';
/// Wrapper around a MediaStreamTrack with additional metadata. /// Wrapper around a MediaStreamTrack with additional metadata.
/// Base for [AudioTrack] and [VideoTrack], /// Base for [AudioTrack] and [VideoTrack],
@@ -17,10 +18,11 @@ abstract class Track extends DisposableChangeNotifier
with EventsEmittable<TrackEvent> { with EventsEmittable<TrackEvent> {
static const uuid = Uuid(); static const uuid = Uuid();
static const cameraName = 'camera'; static const cameraName = 'camera';
static const screenShareName = 'screen'; static const screenShareName = 'screenshare';
final String name; final String name;
final lk_models.TrackType kind; final lk_models.TrackType kind;
final TrackSource source;
rtc.MediaStreamTrack mediaStreamTrack; rtc.MediaStreamTrack mediaStreamTrack;
String? sid; String? sid;
@@ -33,6 +35,7 @@ abstract class Track extends DisposableChangeNotifier
Track( Track(
this.kind, this.kind,
this.source,
this.name, this.name,
this.mediaStreamTrack, this.mediaStreamTrack,
) { ) {
+4 -1
View File
@@ -1,6 +1,7 @@
import '../support/disposable.dart'; import '../support/disposable.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../types.dart'; import '../types.dart';
import '../extensions.dart';
import 'track.dart'; import 'track.dart';
/// Represents a track that's published to the server. This class contains /// 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 sid;
final String name; final String name;
final lk_models.TrackType kind; final lk_models.TrackType kind;
final TrackSource source;
Track? track; Track? track;
bool muted = false; bool muted = false;
@@ -24,7 +26,8 @@ abstract class TrackPublication extends Disposable {
TrackPublication.fromInfo(lk_models.TrackInfo info) TrackPublication.fromInfo(lk_models.TrackInfo info)
: sid = info.sid, : sid = info.sid,
name = info.name, name = info.name,
kind = info.type { kind = info.type,
source = info.source.toLKType() {
updateFromInfo(info); updateFromInfo(info);
} }
+4 -2
View File
@@ -1,8 +1,9 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:livekit_client/src/internal/events.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../internal/events.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../types.dart';
import 'track.dart'; import 'track.dart';
/// A video track will notify when its mediaTrack has changed. /// A video track will notify when its mediaTrack has changed.
@@ -10,12 +11,13 @@ abstract class VideoTrack extends Track {
rtc.MediaStream _mediaStream; rtc.MediaStream _mediaStream;
VideoTrack( VideoTrack(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack mediaTrack, rtc.MediaStreamTrack mediaTrack,
this._mediaStream, this._mediaStream,
// this._client,
) : super( ) : super(
lk_models.TrackType.VIDEO, lk_models.TrackType.VIDEO,
source,
name, name,
mediaTrack, mediaTrack,
); );
+8
View File
@@ -29,6 +29,14 @@ enum Reliability {
lossy, lossy,
} }
enum TrackSource {
unknown,
camera,
microphone,
screenShareVideo,
screenShareAudio,
}
enum CloseReason { enum CloseReason {
network, network,
// ... // ...
+2 -1
View File
@@ -1,12 +1,13 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:livekit_client/livekit_client.dart';
import 'package:visibility_detector/visibility_detector.dart'; import 'package:visibility_detector/visibility_detector.dart';
import '../events.dart';
import '../extensions.dart'; import '../extensions.dart';
import '../internal/events.dart'; import '../internal/events.dart';
import '../logger.dart'; import '../logger.dart';
import '../managers/event.dart';
import '../track/local_video_track.dart'; import '../track/local_video_track.dart';
import '../track/video_track.dart'; import '../track/video_track.dart';