diff --git a/.gitignore b/.gitignore index 622ffa4..0579b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,6 @@ _build # FVM .fvm/ + +# flutter web +lib/generated_plugin_registrant.dart diff --git a/example/lib/widgets/controls.dart b/example/lib/widgets/controls.dart index c301a28..dcdfc9b 100644 --- a/example/lib/widgets/controls.dart +++ b/example/lib/widgets/controls.dart @@ -169,7 +169,7 @@ class _ControlsWidgetState extends State { print('could not publish video: $e'); } } - await participant.setScreenShareEnabled(true); + await participant.setScreenShareEnabled(true, captureScreenAudio: true); } void _disableScreenShare() async { diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index c662ec5..f99b2ba 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -15,6 +15,7 @@ import '../publication/local.dart'; import '../track/local/audio.dart'; import '../track/local/local.dart'; import '../track/local/video.dart'; +import '../track/options.dart'; import '../types/other.dart'; import '../types/participant_permissions.dart'; import '../types/video_dimensions.dart'; @@ -283,14 +284,17 @@ class LocalParticipant extends Participant { } /// Shortcut for publishing a [TrackSource.screenShareVideo] - Future setScreenShareEnabled(bool enabled) async { - return setSourceEnabled(TrackSource.screenShareVideo, enabled); + Future setScreenShareEnabled(bool enabled, + {bool? captureScreenAudio}) async { + return setSourceEnabled(TrackSource.screenShareVideo, enabled, + captureScreenAudio: captureScreenAudio); } /// A convenience method to publish a track for a specific [TrackSource]. /// This is the recommended method to publish tracks. Future setSourceEnabled( - TrackSource source, bool enabled) async { + TrackSource source, bool enabled, + {bool? captureScreenAudio}) async { logger.fine('setSourceEnabled(source: $source, enabled: $enabled)'); final publication = getTrackPublicationBySource(source); if (publication != null) { @@ -314,6 +318,25 @@ class LocalParticipant extends Participant { room.roomOptions.defaultAudioCaptureOptions); return await publishAudioTrack(track); } else if (source == TrackSource.screenShareVideo) { + /// When capturing chrome table audio, we can't capture audio/video + /// track separately, it has to be returned once in getDisplayMedia, + /// so we publish it twice here, but only return videoTrack to user. + if (captureScreenAudio != null) { + final tracks = await LocalVideoTrack.createScreenShareTracksWithAudio( + ScreenShareCaptureOptions( + captureScreenAudio: captureScreenAudio)); + LocalTrackPublication? publication; + for (final track in tracks) { + if (track is LocalVideoTrack) { + publication = await publishVideoTrack(track); + } else if (track is LocalAudioTrack) { + await publishAudioTrack(track); + } + } + + /// just return the video track publication + return publication; + } final track = await LocalVideoTrack.createScreenShareTrack( room.roomOptions.defaultScreenShareCaptureOptions); return await publishVideoTrack(track); diff --git a/lib/src/track/local/audio.dart b/lib/src/track/local/audio.dart index 3ab25e5..ebe2116 100644 --- a/lib/src/track/local/audio.dart +++ b/lib/src/track/local/audio.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; +import 'package:meta/meta.dart'; import '../../proto/livekit_models.pb.dart' as lk_models; import '../../types/other.dart'; @@ -15,7 +16,8 @@ class LocalAudioTrack extends LocalTrack covariant AudioCaptureOptions currentOptions; // private constructor - LocalAudioTrack._( + @internal + LocalAudioTrack( String name, TrackSource source, rtc.MediaStream stream, @@ -36,7 +38,7 @@ class LocalAudioTrack extends LocalTrack options ??= const AudioCaptureOptions(); final stream = await LocalTrack.createStream(options); - return LocalAudioTrack._( + return LocalAudioTrack( '', TrackSource.microphone, stream, diff --git a/lib/src/track/local/local.dart b/lib/src/track/local/local.dart index 3e1b1a8..c8dceb7 100644 --- a/lib/src/track/local/local.dart +++ b/lib/src/track/local/local.dart @@ -119,7 +119,9 @@ abstract class LocalTrack extends Track { final constraints = { 'audio': options is AudioCaptureOptions ? options.toMediaConstraintsMap() - : false, + : options is ScreenShareCaptureOptions + ? (options).captureScreenAudio + : false, 'video': options is VideoCaptureOptions ? options.toMediaConstraintsMap() : false, diff --git a/lib/src/track/local/video.dart b/lib/src/track/local/video.dart index ff8417a..f1dad06 100644 --- a/lib/src/track/local/video.dart +++ b/lib/src/track/local/video.dart @@ -5,6 +5,7 @@ import '../../proto/livekit_models.pb.dart' as lk_models; import '../../types/other.dart'; import '../options.dart'; import '../track.dart'; +import 'audio.dart'; import 'local.dart'; /// A video track from the local device. Use static methods in this class to create @@ -63,6 +64,39 @@ class LocalVideoTrack extends LocalTrack with VideoTrack { options, ); } + + /// Creates a LocalTracks(audio/video) from the display. + /// + /// The current API is mainly used to capture audio when chrome captures tab, + /// but in the future it can also be used for flutter native to open audio + /// capture device when capturing screen + static Future> createScreenShareTracksWithAudio([ + ScreenShareCaptureOptions? options, + ]) async { + options ??= const ScreenShareCaptureOptions(captureScreenAudio: true); + + final stream = await LocalTrack.createStream(options); + + List tracks = [ + LocalVideoTrack._( + Track.screenShareName, + TrackSource.screenShareVideo, + stream, + stream.getVideoTracks().first, + options, + ) + ]; + + if (stream.getAudioTracks().isNotEmpty) { + tracks.add(LocalAudioTrack( + Track.screenShareName, + TrackSource.screenShareAudio, + stream, + stream.getAudioTracks().first, + const AudioCaptureOptions())); + } + return tracks; + } } // diff --git a/lib/src/track/options.dart b/lib/src/track/options.dart index 5cc04e3..a985850 100644 --- a/lib/src/track/options.dart +++ b/lib/src/track/options.dart @@ -82,8 +82,11 @@ class ScreenShareCaptureOptions extends VideoCaptureOptions { /// See instructions on how to setup your Broadcast Extension here: /// https://github.com/flutter-webrtc/flutter-webrtc/wiki/iOS-Screen-Sharing#broadcast-extension-quick-setup final bool useiOSBroadcastExtension; + final bool captureScreenAudio; + const ScreenShareCaptureOptions({ this.useiOSBroadcastExtension = false, + this.captureScreenAudio = false, String? sourceId, double? maxFrameRate, VideoParameters params = VideoParametersPresets.screenShareH720FPS15, @@ -91,6 +94,7 @@ class ScreenShareCaptureOptions extends VideoCaptureOptions { ScreenShareCaptureOptions.from( {this.useiOSBroadcastExtension = false, + this.captureScreenAudio = false, required VideoCaptureOptions captureOptions}) : super(params: captureOptions.params);