Release camera/mic when muted, restartTrack when unmuted (#39)

* re-structure tracks

* publication.track is now a LocalTrack or RemoteTrack

* restartTrack when unmute

* stop/start on restartTrack

* move to mute/unmute

* format

* remove comment
This commit is contained in:
Hiroshi Horie
2021-11-30 15:58:19 +07:00
committed by GitHub
parent 9e0a3b19fe
commit 8037d80674
18 changed files with 279 additions and 239 deletions
+2 -3
View File
@@ -104,10 +104,9 @@ class _ControlsWidgetState extends State<ControlsWidget> {
await FlutterBackground.initialize(androidConfig: androidConfig); await FlutterBackground.initialize(androidConfig: androidConfig);
await FlutterBackground.enableBackgroundExecution(); await FlutterBackground.enableBackgroundExecution();
final screenTrack = final screenShareTrack = await LocalVideoTrack.createScreenShareTrack();
await LocalVideoTrack.createScreenTrack(); // Defaults to camera
await widget.room.localParticipant.publishVideoTrack( await widget.room.localParticipant.publishVideoTrack(
screenTrack, screenShareTrack,
); );
} catch (e) { } catch (e) {
print('could not publish video: $e'); print('could not publish video: $e');
+1 -1
View File
@@ -16,10 +16,10 @@ export 'src/room.dart';
export 'src/track/local_audio_track.dart'; export 'src/track/local_audio_track.dart';
export 'src/track/local_track_publication.dart'; export 'src/track/local_track_publication.dart';
export 'src/track/local_video_track.dart'; export 'src/track/local_video_track.dart';
export 'src/track/local_track.dart';
export 'src/track/options.dart'; export 'src/track/options.dart';
export 'src/track/remote_track_publication.dart'; export 'src/track/remote_track_publication.dart';
export 'src/track/track.dart'; export 'src/track/track.dart';
export 'src/track/track_publication.dart'; export 'src/track/track_publication.dart';
export 'src/track/video_track.dart';
export 'src/types.dart'; export 'src/types.dart';
export 'src/widget/video_track_renderer.dart'; export 'src/widget/video_track_renderer.dart';
+1 -1
View File
@@ -279,7 +279,7 @@ extension LocalParticipantTrackSourceExt on LocalParticipant {
final track = await LocalAudioTrack.create(); final track = await LocalAudioTrack.create();
await publishAudioTrack(track); await publishAudioTrack(track);
} else if (source == TrackSource.screenShareVideo) { } else if (source == TrackSource.screenShareVideo) {
final track = await LocalVideoTrack.createScreenTrack(); final track = await LocalVideoTrack.createScreenShareTrack();
await publishVideoTrack(track); await publishVideoTrack(track);
} }
} }
+2 -2
View File
@@ -104,10 +104,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.source, pub.name, mediaTrack, stream); track = RemoteAudioTrack(pub.name, pub.source, stream, mediaTrack);
} else { } else {
// video track // video track
track = RemoteVideoTrack(pub.source, pub.name, mediaTrack, stream); track = RemoteVideoTrack(pub.name, pub.source, stream, mediaTrack);
} }
await track.start(); await track.start();
@@ -1,16 +1,14 @@
// import 'package:audio_session/audio_session.dart' as _as; // import 'package:audio_session/audio_session.dart' as _as;
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:synchronized/synchronized.dart' as sync; import 'package:synchronized/synchronized.dart' as sync;
import '../logger.dart'; import '../logger.dart';
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 'local_track.dart';
enum AudioTrackState { enum AudioTrackState {
none, none,
@@ -22,7 +20,7 @@ enum AudioTrackState {
typedef ConfigureNativeAudioFunc = Future<NativeAudioConfiguration> Function( typedef ConfigureNativeAudioFunc = Future<NativeAudioConfiguration> Function(
AudioTrackState state); AudioTrackState state);
abstract class AudioTrack extends Track { mixin AudioManagementMixin on AudioTrack {
// it's possible to set custom function here to customize audio session configuration // it's possible to set custom function here to customize audio session configuration
static ConfigureNativeAudioFunc nativeAudioConfigurationForAudioTrackState = static ConfigureNativeAudioFunc nativeAudioConfigurationForAudioTrackState =
defaultNativeAudioConfigurationFunc; defaultNativeAudioConfigurationFunc;
@@ -32,20 +30,6 @@ abstract class AudioTrack extends Track {
static int _localTrackCount = 0; static int _localTrackCount = 0;
static int _remoteTrackCount = 0; static int _remoteTrackCount = 0;
rtc.MediaStream? mediaStream;
AudioTrack(
TrackSource source,
String name,
rtc.MediaStreamTrack track,
this.mediaStream,
) : super(
lk_models.TrackType.AUDIO,
source,
name,
track,
);
/// Start playing audio track. On web platform, create an audio element and /// Start playing audio track. On web platform, create an audio element and
/// start playback /// start playback
@override @override
@@ -149,17 +133,6 @@ Future<NativeAudioConfiguration> defaultNativeAudioConfigurationFunc(
); );
} }
// TODO: .record category causes exception in WebRTC lib for unknown reason
// if (this == AudioTrackState.localOnly) {
// return NativeAudioConfiguration(
// iosCategory: IosAudioCategory.record,
// iosCategoryOptions: {
// // IosAudioCategoryOption.allowBluetooth,
// },
// iosMode: IosAudioMode.spokenAudio,
// );
// }
return NativeAudioConfiguration( return NativeAudioConfiguration(
appleAudioCategory: AppleAudioCategory.soloAmbient, appleAudioCategory: AppleAudioCategory.soloAmbient,
appleAudioCategoryOptions: {}, appleAudioCategoryOptions: {},
+25 -30
View File
@@ -2,50 +2,45 @@ import 'dart:async';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import '../exceptions.dart'; import '../proto/livekit_models.pb.dart' as lk_models;
import '../logger.dart';
import '../types.dart'; import '../types.dart';
import 'audio_track.dart'; import 'audio_management.dart';
import 'local_track.dart'; import 'local_track.dart';
import 'options.dart'; import 'options.dart';
class LocalAudioTrack extends AudioTrack with LocalTrack { class LocalAudioTrack extends LocalTrack with AudioTrack, AudioManagementMixin {
// Options used for this track
@override
covariant LocalAudioTrackOptions currentOptions;
// private constructor // private constructor
LocalAudioTrack._( LocalAudioTrack._(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack track, TrackSource source,
rtc.MediaStream stream, rtc.MediaStream stream,
) : super(source, name, track, stream); rtc.MediaStreamTrack track,
this.currentOptions,
) : super(
name,
lk_models.TrackType.AUDIO,
source,
stream,
track,
);
/// 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([
[LocalAudioTrackOptions? options]) async { LocalAudioTrackOptions? options,
final audioConstraints = options?.toMediaConstraintsMap() ?? ]) async {
const LocalAudioTrackOptions().toMediaConstraintsMap(); options ??= const LocalAudioTrackOptions();
final stream = final stream = await LocalTrack.createStream(options);
await rtc.navigator.mediaDevices.getUserMedia(<String, dynamic>{
'audio': audioConstraints,
'video': false,
});
if (stream.getAudioTracks().isEmpty) throw TrackCreateException();
return LocalAudioTrack._( return LocalAudioTrack._(
TrackSource.microphone,
'', '',
stream.getAudioTracks().first, TrackSource.microphone,
stream, stream,
stream.getAudioTracks().first,
options,
); );
} }
@override
Future<bool> stop() async {
final didStop = await super.stop();
if (didStop) {
logger.fine('Stopping mediaStreamTrack...');
await mediaStreamTrack.stop();
}
return didStop;
}
} }
+103 -1
View File
@@ -1,13 +1,41 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:meta/meta.dart';
import '../exceptions.dart';
import '../internal/events.dart'; import '../internal/events.dart';
import '../logger.dart'; import '../logger.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import '../types.dart';
import 'options.dart';
import 'track.dart'; import 'track.dart';
mixin LocalTrack on Track { mixin VideoTrack on Track {}
mixin AudioTrack on Track {}
abstract class LocalTrack extends Track {
// Options used for this track
abstract LocalTrackOptions currentOptions;
LocalTrack(
String name,
lk_models.TrackType kind,
TrackSource source,
rtc.MediaStream mediaStream,
rtc.MediaStreamTrack mediaStreamTrack,
) : super(
name,
kind,
source,
mediaStream,
mediaStreamTrack,
);
// only local tracks can set muted // only local tracks can set muted
Future<void> mute() async { Future<void> mute() async {
logger.fine('LocalTrack.mute() muted: $muted'); logger.fine('LocalTrack.mute() muted: $muted');
if (muted) return; if (muted) return;
await disable(); await disable();
await stop();
updateMuted(true); updateMuted(true);
events.emit(TrackMuteUpdatedEvent(track: this, muted: muted)); events.emit(TrackMuteUpdatedEvent(track: this, muted: muted));
} }
@@ -15,8 +43,82 @@ mixin LocalTrack on Track {
Future<void> unmute() async { Future<void> unmute() async {
logger.fine('LocalTrack.unmute() muted: $muted'); logger.fine('LocalTrack.unmute() muted: $muted');
if (!muted) return; if (!muted) return;
await restartTrack();
await enable(); await enable();
updateMuted(false); updateMuted(false);
events.emit(TrackMuteUpdatedEvent(track: this, muted: muted)); events.emit(TrackMuteUpdatedEvent(track: this, muted: muted));
} }
@override
Future<bool> stop() async {
final didStop = await super.stop();
if (didStop) {
logger.fine('Stopping mediaStreamTrack...');
await mediaStreamTrack.stop();
await mediaStream.dispose();
}
return didStop;
}
/// Creates a [rtc.MediaStream] from LocalTrackOptions.
@internal
static Future<rtc.MediaStream> createStream(
LocalTrackOptions options,
) async {
final constraints = <String, dynamic>{
'audio': options is LocalAudioTrackOptions
? options.toMediaConstraintsMap()
: false,
'video': options is LocalVideoTrackOptions
? options.toMediaConstraintsMap()
: false,
};
final rtc.MediaStream stream;
if (options is ScreenShareTrackOptions) {
stream = await rtc.navigator.mediaDevices.getDisplayMedia(constraints);
} else {
// options is CameraVideoTrackOptions
stream = await rtc.navigator.mediaDevices.getUserMedia(constraints);
}
// Check if the stream looks good
if ((options is LocalVideoTrackOptions &&
stream.getVideoTracks().isEmpty) ||
(options is LocalAudioTrackOptions &&
stream.getAudioTracks().isEmpty)) {
throw TrackCreateException(
'Failed to create stream, at least 1 video or audio track should exist');
}
return stream;
}
/// Restarts the track with new options. This is useful when switching between
/// front and back cameras.
Future<void> restartTrack([
LocalTrackOptions? options,
]) async {
if (sender == null) throw TrackCreateException('could not restart track');
if (options != null && currentOptions.runtimeType != options.runtimeType) {
throw Exception('options must be a ${currentOptions.runtimeType}');
}
currentOptions = options ?? currentOptions;
// stop if not already stopped...
await stop();
// create new track with options
final newStream = await LocalTrack.createStream(currentOptions);
final newTrack = newStream.getTracks().first;
// replace track on sender
await sender?.replaceTrack(newTrack);
// set new stream & track to this object
updateMediaStreamAndTrack(newStream, newTrack);
// mark as started
await start();
}
} }
@@ -10,6 +10,9 @@ import 'track_publication.dart';
class LocalTrackPublication extends TrackPublication { class LocalTrackPublication extends TrackPublication {
final LocalParticipant _participant; final LocalParticipant _participant;
@override
covariant LocalTrack? track;
LocalTrackPublication( LocalTrackPublication(
lk_models.TrackInfo info, lk_models.TrackInfo info,
Track track, Track track,
+22 -80
View File
@@ -1,73 +1,45 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import '../exceptions.dart';
import '../logger.dart'; import '../logger.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import '../types.dart'; import '../types.dart';
import 'local_track.dart'; import 'local_track.dart';
import 'options.dart'; import 'options.dart';
import 'track.dart'; import 'track.dart';
import 'video_track.dart';
/// A video track from the local device. Use static methods in this class to create /// A video track from the local device. Use static methods in this class to create
/// video tracks. /// video tracks.
class LocalVideoTrack extends VideoTrack with LocalTrack { class LocalVideoTrack extends LocalTrack with VideoTrack {
//
// Options used for this track // Options used for this track
// @override
LocalVideoTrackOptions currentOptions; covariant LocalVideoTrackOptions currentOptions;
//
// Private constructor // Private constructor
//
LocalVideoTrack._( LocalVideoTrack._(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack mediaTrack, TrackSource source,
rtc.MediaStream stream, rtc.MediaStream stream,
rtc.MediaStreamTrack track,
this.currentOptions, this.currentOptions,
) : super(source, name, mediaTrack, stream); ) : super(
name,
rtc.RTCRtpSender? get sender => transceiver?.sender; lk_models.TrackType.VIDEO,
source,
/// Restarts the track with new options. This is useful when switching between stream,
/// front and back cameras. track,
Future<void> restartTrack([ );
LocalVideoTrackOptions? options,
]) async {
if (sender == null) throw TrackCreateException('could not restart track');
if (options != null && currentOptions.runtimeType != options.runtimeType) {
throw Exception('options must be a ${currentOptions.runtimeType}');
}
currentOptions = options ?? currentOptions;
// stop the current track & stream
await mediaStreamTrack.stop();
await mediaStream.dispose();
// create new track with options
final newStream = await _createStream(currentOptions);
final newTrack = newStream.getVideoTracks().first;
// replace track on sender
await sender?.replaceTrack(newTrack);
// set new stream & track to this object
setMediaStream(newStream);
mediaStreamTrack = newTrack;
}
/// Creates a LocalVideoTrack from camera input. /// Creates a LocalVideoTrack from camera input.
static Future<LocalVideoTrack> createCameraTrack([ static Future<LocalVideoTrack> createCameraTrack([
CameraTrackOptions? options, CameraTrackOptions? options,
]) async { ]) async {
options ??= const CameraTrackOptions(); options ??= const CameraTrackOptions();
final stream = await _createStream(options); final stream = await LocalTrack.createStream(options);
return LocalVideoTrack._( return LocalVideoTrack._(
TrackSource.camera,
Track.cameraName, Track.cameraName,
stream.getVideoTracks().first, TrackSource.camera,
stream, stream,
stream.getVideoTracks().first,
options, options,
); );
} }
@@ -76,49 +48,19 @@ class LocalVideoTrack extends VideoTrack with LocalTrack {
/// ///
/// Note: Android requires a foreground service to be started prior to /// Note: Android requires a foreground service to be started prior to
/// creating a screen track. Refer to the example app for an implementation. /// creating a screen track. Refer to the example app for an implementation.
static Future<LocalVideoTrack> createScreenTrack([ static Future<LocalVideoTrack> createScreenShareTrack([
ScreenTrackOptions? options, ScreenShareTrackOptions? options,
]) async { ]) async {
options ??= const ScreenTrackOptions(); options ??= const ScreenShareTrackOptions();
final stream = await _createStream(options); final stream = await LocalTrack.createStream(options);
return LocalVideoTrack._( return LocalVideoTrack._(
TrackSource.screenShareVideo,
Track.screenShareName, Track.screenShareName,
stream.getVideoTracks().first, TrackSource.screenShareVideo,
stream, stream,
stream.getVideoTracks().first,
options, options,
); );
} }
static Future<rtc.MediaStream> _createStream(
LocalVideoTrackOptions options,
) async {
final constraints = <String, dynamic>{
'audio': false,
'video': options.toMediaConstraintsMap(),
};
final rtc.MediaStream stream;
if (options is ScreenTrackOptions) {
stream = await rtc.navigator.mediaDevices.getDisplayMedia(constraints);
} else {
// options is CameraVideoTrackOptions
stream = await rtc.navigator.mediaDevices.getUserMedia(constraints);
}
if (stream.getVideoTracks().isEmpty) throw TrackCreateException();
return stream;
}
@override
Future<bool> stop() async {
final didStop = await super.stop();
if (didStop) {
logger.fine('Stopping mediaStreamTrack...');
await mediaStreamTrack.stop();
}
return didStop;
}
} }
// //
+13 -4
View File
@@ -43,12 +43,19 @@ class CameraTrackOptions extends LocalVideoTrackOptions {
); );
} }
class ScreenTrackOptions extends LocalVideoTrackOptions { class ScreenShareTrackOptions extends LocalVideoTrackOptions {
const ScreenTrackOptions(); const ScreenShareTrackOptions();
}
/// Base class for track options.
abstract class LocalTrackOptions {
const LocalTrackOptions();
// All subclasses must be able to report constraints
Map<String, dynamic> toMediaConstraintsMap();
} }
/// Options when creating a LocalVideoTrack. /// Options when creating a LocalVideoTrack.
abstract class LocalVideoTrackOptions { abstract class LocalVideoTrackOptions extends LocalTrackOptions {
// final LocalVideoTrackType type; // final LocalVideoTrackType type;
final VideoParameters params; final VideoParameters params;
@@ -56,6 +63,7 @@ abstract class LocalVideoTrackOptions {
this.params = VideoParameters.presetQHD169, this.params = VideoParameters.presetQHD169,
}); });
@override
Map<String, dynamic> toMediaConstraintsMap() => <String, dynamic>{ Map<String, dynamic> toMediaConstraintsMap() => <String, dynamic>{
'mandatory': params.toMediaConstraintsMap(), 'mandatory': params.toMediaConstraintsMap(),
}; };
@@ -235,7 +243,7 @@ class VideoParameters {
} }
/// Options when creating an LocalAudioTrack. Placeholder for now. /// Options when creating an LocalAudioTrack. Placeholder for now.
class LocalAudioTrackOptions { class LocalAudioTrackOptions extends LocalTrackOptions {
final bool noiseSuppression; final bool noiseSuppression;
final bool echoCancellation; final bool echoCancellation;
final bool autoGainControl; final bool autoGainControl;
@@ -250,6 +258,7 @@ class LocalAudioTrackOptions {
this.typingNoiseDetection = true, this.typingNoiseDetection = true,
}); });
@override
Map<String, dynamic> toMediaConstraintsMap() => <String, dynamic>{ Map<String, dynamic> toMediaConstraintsMap() => <String, dynamic>{
'optional': <Map<String, dynamic>>[ 'optional': <Map<String, dynamic>>[
<String, dynamic>{'echoCancellation': echoCancellation}, <String, dynamic>{'echoCancellation': echoCancellation},
+16 -7
View File
@@ -1,16 +1,27 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import '../proto/livekit_models.pb.dart' as lk_models;
import '../types.dart'; 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_management.dart';
import 'local_track.dart';
import 'remote_track.dart';
class RemoteAudioTrack extends AudioTrack { class RemoteAudioTrack extends RemoteTrack
with AudioTrack, AudioManagementMixin {
// //
RemoteAudioTrack( RemoteAudioTrack(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack track, TrackSource source,
rtc.MediaStream stream, rtc.MediaStream stream,
) : super(source, name, track, stream); rtc.MediaStreamTrack track,
) : super(
name,
lk_models.TrackType.AUDIO,
source,
stream,
track,
);
@override @override
Future<bool> start() async { Future<bool> start() async {
@@ -18,7 +29,6 @@ class RemoteAudioTrack extends AudioTrack {
if (didStart) { if (didStart) {
// web support // web support
audio.startAudio(getCid(), mediaStreamTrack); audio.startAudio(getCid(), mediaStreamTrack);
await enable();
} }
return didStart; return didStart;
} }
@@ -29,7 +39,6 @@ class RemoteAudioTrack extends AudioTrack {
if (didStop) { if (didStop) {
// web support // web support
audio.stopAudio(getCid()); audio.stopAudio(getCid());
await disable();
} }
return didStop; return didStop;
} }
+39
View File
@@ -0,0 +1,39 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import '../proto/livekit_models.pb.dart' as lk_models;
import '../types.dart';
import 'track.dart';
abstract class RemoteTrack extends Track {
RemoteTrack(
String name,
lk_models.TrackType kind,
TrackSource source,
rtc.MediaStream stream,
rtc.MediaStreamTrack track,
) : super(
name,
kind,
source,
stream,
track,
);
@override
Future<bool> start() async {
final didStart = await super.start();
if (didStart) {
await enable();
}
return didStart;
}
@override
Future<bool> stop() async {
final didStop = await super.stop();
if (didStop) {
await disable();
}
return didStop;
}
}
@@ -12,6 +12,7 @@ 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;
import '../utils.dart'; import '../utils.dart';
import 'remote_track.dart';
import 'track.dart'; import 'track.dart';
import 'track_publication.dart'; import 'track_publication.dart';
@@ -31,6 +32,9 @@ class RendererVisibility {
/// Represents a track publication from a RemoteParticipant. Provides methods to /// Represents a track publication from a RemoteParticipant. Provides methods to
/// control if we should subscribe to the track, and its quality (for video). /// control if we should subscribe to the track, and its quality (for video).
class RemoteTrackPublication extends TrackPublication { class RemoteTrackPublication extends TrackPublication {
@override
covariant RemoteTrack? track;
final RemoteParticipant _participant; final RemoteParticipant _participant;
bool _enabled = true; bool _enabled = true;
lk_rtc.VideoQuality _videoQuality = lk_rtc.VideoQuality.HIGH; lk_rtc.VideoQuality _videoQuality = lk_rtc.VideoQuality.HIGH;
+13 -23
View File
@@ -1,32 +1,22 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:livekit_client/src/track/local_track.dart';
import '../track/video_track.dart'; import '../proto/livekit_models.pb.dart' as lk_models;
import '../types.dart'; import '../types.dart';
import 'remote_track.dart';
class RemoteVideoTrack extends VideoTrack { class RemoteVideoTrack extends RemoteTrack with VideoTrack {
// //
RemoteVideoTrack( RemoteVideoTrack(
TrackSource source,
String name, String name,
rtc.MediaStreamTrack mediaTrack, TrackSource source,
rtc.MediaStream stream, rtc.MediaStream stream,
) : super(source, name, mediaTrack, stream); rtc.MediaStreamTrack track,
) : super(
@override name,
Future<bool> start() async { lk_models.TrackType.VIDEO,
final didStart = await super.start(); source,
if (didStart) { stream,
await enable(); track,
} );
return didStart;
}
@override
Future<bool> stop() async {
final didStop = await super.stop();
if (didStop) {
await disable();
}
return didStop;
}
} }
+25 -3
View File
@@ -5,6 +5,7 @@ import 'package:uuid/uuid.dart';
import '../events.dart'; import '../events.dart';
import '../extensions.dart'; import '../extensions.dart';
import '../internal/events.dart';
import '../logger.dart'; 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;
@@ -23,7 +24,14 @@ abstract class Track extends DisposableChangeNotifier
final String name; final String name;
final lk_models.TrackType kind; final lk_models.TrackType kind;
final TrackSource source; final TrackSource source;
rtc.MediaStreamTrack mediaStreamTrack;
// read only
rtc.MediaStream get mediaStream => _mediaStream;
rtc.MediaStream _mediaStream;
// read only
rtc.MediaStreamTrack get mediaStreamTrack => _mediaStreamTrack;
rtc.MediaStreamTrack _mediaStreamTrack;
String? sid; String? sid;
rtc.RTCRtpTransceiver? transceiver; rtc.RTCRtpTransceiver? transceiver;
@@ -36,11 +44,14 @@ abstract class Track extends DisposableChangeNotifier
bool _muted = false; bool _muted = false;
bool get muted => _muted; bool get muted => _muted;
rtc.RTCRtpSender? get sender => transceiver?.sender;
Track( Track(
this.name,
this.kind, this.kind,
this.source, this.source,
this.name, this._mediaStream,
this.mediaStreamTrack, this._mediaStreamTrack,
) { ) {
// Any event emitted will trigger ChangeNotifier // Any event emitted will trigger ChangeNotifier
events.listen((event) { events.listen((event) {
@@ -128,4 +139,15 @@ abstract class Track extends DisposableChangeNotifier
@internal @internal
void updateMuted(bool muted) => _muted = muted; void updateMuted(bool muted) => _muted = muted;
@internal
void updateMediaStreamAndTrack(
rtc.MediaStream stream, rtc.MediaStreamTrack track) {
_mediaStream = stream;
_mediaStreamTrack = track;
events.emit(TrackStreamUpdatedEvent(
track: this,
stream: stream,
));
}
} }
+6 -6
View File
@@ -18,8 +18,8 @@ abstract class TrackPublication extends Disposable {
final lk_models.TrackType kind; final lk_models.TrackType kind;
final TrackSource source; final TrackSource source;
Track? _track; abstract Track? track;
Track? get track => _track; // Track? get track => _track;
// metadata-muted // metadata-muted
bool _muted = false; bool _muted = false;
@@ -28,7 +28,7 @@ abstract class TrackPublication extends Disposable {
bool simulcasted = false; bool simulcasted = false;
TrackDimension? dimension; TrackDimension? dimension;
bool get subscribed => _track != null; bool get subscribed => track != null;
TrackPublication.fromInfo(lk_models.TrackInfo info) TrackPublication.fromInfo(lk_models.TrackInfo info)
: sid = info.sid, : sid = info.sid,
@@ -66,10 +66,10 @@ abstract class TrackPublication extends Disposable {
// Intended for internal use only. // Intended for internal use only.
@internal @internal
Future<bool> updateTrack(Track? newValue) async { Future<bool> updateTrack(Track? newValue) async {
if (_track == newValue) return false; if (track == newValue) return false;
// dispose previous track (if exists) // dispose previous track (if exists)
await _track?.dispose(); await track?.dispose();
_track = newValue; track = newValue;
return true; return true;
} }
} }
-47
View File
@@ -1,47 +0,0 @@
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
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.
abstract class VideoTrack extends Track {
rtc.MediaStream _mediaStream;
VideoTrack(
TrackSource source,
String name,
rtc.MediaStreamTrack mediaTrack,
this._mediaStream,
) : super(
lk_models.TrackType.VIDEO,
source,
name,
mediaTrack,
);
rtc.MediaStream get mediaStream => _mediaStream;
/// internal use
/// {@nodoc}
@internal
void setMediaStream(rtc.MediaStream stream) {
_mediaStream = stream;
events.emit(TrackStreamUpdatedEvent(
track: this,
stream: stream,
));
}
@override
Future<bool> stop() async {
final didStop = await super.stop();
if (didStop) {
await _mediaStream.dispose();
}
// _mediaStream = null;
return didStop;
}
}
+1 -1
View File
@@ -1,6 +1,7 @@
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/src/track/local_track.dart';
import 'package:visibility_detector/visibility_detector.dart'; import 'package:visibility_detector/visibility_detector.dart';
import '../events.dart'; import '../events.dart';
@@ -9,7 +10,6 @@ import '../internal/events.dart';
import '../logger.dart'; import '../logger.dart';
import '../managers/event.dart'; import '../managers/event.dart';
import '../track/local_video_track.dart'; import '../track/local_video_track.dart';
import '../track/video_track.dart';
/// Widget that renders a [VideoTrack]. /// Widget that renders a [VideoTrack].
class VideoTrackRenderer extends StatefulWidget { class VideoTrackRenderer extends StatefulWidget {