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:
@@ -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');
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {},
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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},
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user