Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
android {
namespace = "io.livekit.fluttercomponents.example.example"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
ndkVersion = "27.0.12077973"

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
Expand Down
67 changes: 29 additions & 38 deletions lib/livekit_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,73 +15,64 @@
library livekit_components;

export 'src/context/chat_context.dart';
export 'src/context/room_context.dart';
export 'src/context/media_device_context.dart';
export 'src/context/participant_context.dart';
export 'src/context/room_context.dart';
export 'src/context/track_reference_context.dart';

export 'src/debug/logger.dart';

export 'src/types/agent_state.dart';
export 'src/types/transcription.dart';

export 'src/ui/builder/camera_preview.dart';
export 'src/ui/builder/room/chat_toggle.dart';
export 'src/ui/builder/participant/participant_attributes.dart';
export 'src/ui/builder/participant/participant_kind.dart';
export 'src/ui/builder/participant/participant_loop.dart';
export 'src/ui/builder/participant/participant_metadata.dart';
export 'src/ui/builder/participant/participant_muted_indicator.dart';
export 'src/ui/builder/participant/participant_name.dart';
export 'src/ui/builder/participant/participant_permissions.dart';
export 'src/ui/builder/participant/participant_selector.dart';
export 'src/ui/builder/participant/participant_track.dart';
export 'src/ui/builder/participant/participant_transcription.dart';
export 'src/ui/builder/room/chat.dart';
export 'src/ui/builder/room/chat_toggle.dart';
export 'src/ui/builder/room/disconnect_button.dart';
export 'src/ui/builder/room/join_button.dart';
export 'src/ui/builder/room/media_device_select_button.dart';
export 'src/ui/builder/room/media_device.dart';
export 'src/ui/builder/room/media_device_select_button.dart';
export 'src/ui/builder/room/room.dart';
export 'src/ui/builder/room/room_active_recording_indicator.dart';
export 'src/ui/builder/room/room_connection_state.dart';
export 'src/ui/builder/room/room_metadata.dart';
export 'src/ui/builder/room/room_name.dart';
export 'src/ui/builder/room/room_participants.dart';
export 'src/ui/builder/room/room.dart';
export 'src/ui/builder/room/screenshare_toggle.dart';
export 'src/ui/builder/room/room_active_recording_indicator.dart';
export 'src/ui/builder/room/transcription.dart';

export 'src/ui/builder/participant/participant_track.dart';
export 'src/ui/builder/participant/participant_attributes.dart';
export 'src/ui/builder/participant/participant_loop.dart';
export 'src/ui/builder/participant/participant_metadata.dart';
export 'src/ui/builder/participant/participant_muted_indicator.dart';
export 'src/ui/builder/participant/participant_name.dart';
export 'src/ui/builder/participant/participant_kind.dart';
export 'src/ui/builder/participant/participant_permissions.dart';
export 'src/ui/builder/participant/participant_transcription.dart';
export 'src/ui/builder/participant/participant_selector.dart';

export 'src/ui/builder/track/connection_quality_indicator.dart';
export 'src/ui/builder/track/e2e_encryption_indicator.dart';
export 'src/ui/builder/track/is_speaking_indicator.dart';

export 'src/ui/layout/carousel_layout.dart';
export 'src/ui/layout/grid_layout.dart';
export 'src/ui/layout/layouts.dart';
export 'src/ui/prejoin/prejoin.dart';
export 'src/ui/widgets/camera_preview.dart';
export 'src/ui/widgets/participant/connection_quality_indicator.dart';
export 'src/ui/widgets/participant/is_speaking_indicator.dart';
export 'src/ui/widgets/participant/participant_status_bar.dart';
export 'src/ui/widgets/participant/participant_tile_widget.dart';
export 'src/ui/widgets/participant/is_speaking_indicator.dart';

export 'src/ui/widgets/participant/transcription_widget.dart';
export 'src/ui/widgets/room/camera_select_button.dart';
export 'src/ui/widgets/room/chat_toggle.dart';
export 'src/ui/widgets/room/chat_widget.dart';
export 'src/ui/widgets/room/media_device_select_button.dart';
export 'src/ui/widgets/room/microphone_select_button.dart';
export 'src/ui/widgets/room/clear_pin_button.dart';
export 'src/ui/widgets/room/control_bar.dart';
export 'src/ui/widgets/room/disconnect_button.dart';
export 'src/ui/widgets/room/media_device_select_button.dart';
export 'src/ui/widgets/room/microphone_select_button.dart';
export 'src/ui/widgets/room/screenshare_toggle.dart';
export 'src/ui/widgets/room/clear_pin_button.dart';
export 'src/ui/widgets/participant/transcription_widget.dart';

export 'src/ui/widgets/theme.dart';
export 'src/ui/widgets/toast.dart';
export 'src/ui/widgets/track/audio_visualizer_widget.dart';
export 'src/ui/widgets/track/focus_toggle.dart';
export 'src/ui/widgets/track/no_track_widget.dart';
export 'src/ui/widgets/track/video_track_widget.dart';
export 'src/ui/widgets/track/track_stats_widget.dart';
export 'src/ui/widgets/theme.dart';
export 'src/ui/widgets/toast.dart';
export 'src/ui/widgets/camera_preview.dart';

export 'src/ui/layout/layouts.dart';
export 'src/ui/layout/grid_layout.dart';
export 'src/ui/layout/carousel_layout.dart';

export 'src/ui/prejoin/prejoin.dart';
export 'src/ui/widgets/track/video_track_widget.dart';
64 changes: 33 additions & 31 deletions lib/src/context/media_device_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ class MediaDeviceContext extends ChangeNotifier {

Future<void> loadDevices() async {
_loadDevices(await Hardware.instance.enumerateDevices());
_deviceChangeSub =
Hardware.instance.onDeviceChange.stream.listen(_loadDevices);
_deviceChangeSub = Hardware.instance.onDeviceChange.stream.listen(_loadDevices);
}

_loadDevices(List<MediaDevice> devices) {
Expand All @@ -76,14 +75,11 @@ class MediaDeviceContext extends ChangeNotifier {
_videoInputs = devices.where((d) => d.kind == 'videoinput').toList();

selectedAudioInputDeviceId ??=
Hardware.instance.selectedAudioInput?.deviceId ??
_audioInputs?.firstOrNull?.deviceId;
Hardware.instance.selectedAudioInput?.deviceId ?? _audioInputs?.firstOrNull?.deviceId;
selectedVideoInputDeviceId ??=
Hardware.instance.selectedVideoInput?.deviceId ??
_videoInputs?.firstOrNull?.deviceId;
Hardware.instance.selectedVideoInput?.deviceId ?? _videoInputs?.firstOrNull?.deviceId;
selectedAudioOutputDeviceId ??=
Hardware.instance.selectedAudioOutput?.deviceId ??
_audioOutputs?.firstOrNull?.deviceId;
Hardware.instance.selectedAudioOutput?.deviceId ?? _audioOutputs?.firstOrNull?.deviceId;
notifyListeners();
}

Expand Down Expand Up @@ -189,8 +185,7 @@ class MediaDeviceContext extends ChangeNotifier {
notifyListeners();
}

bool get isScreenShareEnabled =>
_room?.localParticipant?.isScreenShareEnabled() ?? false;
bool get isScreenShareEnabled => _room?.localParticipant?.isScreenShareEnabled() ?? false;

Future<void> enableScreenShare(context) async {
if (lkPlatformIsDesktop()) {
Expand Down Expand Up @@ -232,20 +227,16 @@ class MediaDeviceContext extends ChangeNotifier {
notificationTitle: 'Screen Sharing',
notificationText: 'LiveKit Example is sharing the screen.',
notificationImportance: AndroidNotificationImportance.normal,
notificationIcon: AndroidResource(
name: 'livekit_ic_launcher', defType: 'mipmap'),
notificationIcon: AndroidResource(name: 'livekit_ic_launcher', defType: 'mipmap'),
);
hasPermissions = await FlutterBackground.initialize(
androidConfig: androidConfig);
hasPermissions = await FlutterBackground.initialize(androidConfig: androidConfig);
}
if (hasPermissions &&
!FlutterBackground.isBackgroundExecutionEnabled) {
if (hasPermissions && !FlutterBackground.isBackgroundExecutionEnabled) {
await FlutterBackground.enableBackgroundExecution();
}
} catch (e) {
if (!isRetry) {
return await Future<void>.delayed(const Duration(seconds: 1),
() => requestBackgroundPermission(true));
return await Future<void>.delayed(const Duration(seconds: 1), () => requestBackgroundPermission(true));
}
Debug.log('could not publish video: $e');
}
Expand All @@ -265,13 +256,11 @@ class MediaDeviceContext extends ChangeNotifier {
}

if (lkPlatformIsWebMobile()) {
await context
.showErrorDialog('Screen share is not supported on mobile web');
await context.showErrorDialog('Screen share is not supported on mobile web');
return;
}

await _room?.localParticipant
?.setScreenShareEnabled(true, captureScreenAudio: true);
await _room?.localParticipant?.setScreenShareEnabled(true, captureScreenAudio: true);
notifyListeners();
}

Expand All @@ -284,23 +273,19 @@ class MediaDeviceContext extends ChangeNotifier {

bool? get isSpeakerOn => Hardware.instance.speakerOn;

void setSpeakerphoneOn(bool speakerOn,
{bool forceSpeakerOutput = false}) async {
await Hardware.instance
.setSpeakerphoneOn(speakerOn, forceSpeakerOutput: forceSpeakerOutput);
void setSpeakerphoneOn(bool speakerOn, {bool forceSpeakerOutput = false}) async {
await Hardware.instance.setSpeakerphoneOn(speakerOn, forceSpeakerOutput: forceSpeakerOutput);
notifyListeners();
}

CameraPosition? get currentPosition {
final track =
_room?.localParticipant?.videoTrackPublications.firstOrNull?.track;
final track = _room?.localParticipant?.videoTrackPublications.firstOrNull?.track;
if (track == null) return null;
return (track.currentOptions as CameraCaptureOptions).cameraPosition;
}

void switchCamera(CameraPosition newPosition) async {
final track =
_room?.localParticipant?.videoTrackPublications.firstOrNull?.track;
void switchCameraPosition(CameraPosition newPosition) async {
final track = _room?.localParticipant?.videoTrackPublications.firstOrNull?.track;
if (track == null) return;

try {
Expand All @@ -312,6 +297,23 @@ class MediaDeviceContext extends ChangeNotifier {
notifyListeners();
}

void toggleCameraPosition() async {
final track = _room?.localParticipant?.videoTrackPublications.firstOrNull?.track;
if (track == null) return;
final currentPosition = (track.currentOptions as CameraCaptureOptions).cameraPosition;

final newPosition = currentPosition == CameraPosition.back ? CameraPosition.front : CameraPosition.back;

try {
await track.setCameraPosition(newPosition);
} catch (error) {
print('could not restart track: $error');
return;
}

notifyListeners();
}

@override
void dispose() {
super.dispose();
Expand Down
12 changes: 10 additions & 2 deletions lib/src/context/participant_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,25 @@ class ParticipantContext extends ChangeNotifier {
..on<TrackMutedEvent>((event) {
if (event.participant.identity == identity &&
event.publication.kind == TrackType.AUDIO) {
Debug.event('TrackContext: TrackMutedEvent for ${_participant.sid}');
Debug.event('ParticipantContext: TrackMutedEvent for ${_participant.sid}');
notifyListeners();
}
})
..on<TrackUnmutedEvent>((event) {
if (event.participant.identity == identity &&
event.publication.kind == TrackType.AUDIO) {
Debug.event(
'TrackContext: TrackUnmutedEvent for ${_participant.sid}');
'ParticipantContext: TrackUnmutedEvent for ${_participant.sid}');
notifyListeners();
}
})
..on<TrackPublishedEvent>((event) {
Debug.event('ParticipantContext: TrackPublishedEvent for ${_participant.sid}');
notifyListeners();
})
..on<TrackUnpublishedEvent>((event) {
Debug.event('ParticipantContext: TrackUnpublishedEvent for ${_participant.sid}');
notifyListeners();
});

_metadata = _participant.metadata;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/ui/widgets/room/control_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class ControlBar extends StatelessWidget {
CameraSwitchButton(
currentPosition: position,
onToggle: (newPosition) =>
deviceCtx.switchCamera(newPosition),
deviceCtx.switchCameraPosition(newPosition),
)),
if (screenShare)
ScreenShareToggle(
Expand Down
30 changes: 19 additions & 11 deletions lib/src/ui/widgets/track/video_track_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,28 @@
// limitations under the License.

import 'package:flutter/material.dart';

import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_client/livekit_client.dart' as sdk;
import 'package:provider/provider.dart';

import '../../../context/track_reference_context.dart';
import '../../../debug/logger.dart';
import '../theme.dart';
import 'no_track_widget.dart';

class VideoTrackWidget extends StatelessWidget {
final sdk.VideoViewFit fit;
final WidgetBuilder? noTrackBuilder;

const VideoTrackWidget({
super.key,
this.fit = sdk.VideoViewFit.contain,
this.noTrackBuilder,
});

Widget _buildNoTrack(BuildContext ctx) {
if (noTrackBuilder != null) return noTrackBuilder!(ctx);
return const NoTrackWidget();
}

@override
Widget build(BuildContext context) {
var trackCtx = Provider.of<TrackReferenceContext?>(context);
Expand All @@ -39,14 +47,14 @@ class VideoTrackWidget extends StatelessWidget {
}

return Selector<TrackReferenceContext, bool>(
selector: (context, isMuted) => trackCtx.isMuted,
builder: (BuildContext context, isMuted, child) => !isMuted &&
trackCtx.videoTrack != null
? Container(
color: LKColors.lkDarkBlue,
child:
VideoTrackRenderer(trackCtx.videoTrack!, key: ValueKey(sid)))
: const NoTrackWidget(),
selector: (ctx, isMuted) => trackCtx.isMuted,
builder: (BuildContext ctx, isMuted, child) => !isMuted && trackCtx.videoTrack != null
? sdk.VideoTrackRenderer(
trackCtx.videoTrack!,
key: ValueKey(sid),
fit: fit,
)
: _buildNoTrack(ctx),
);
}
}
Loading