android screenshare example and touch ups (#31)
This commit is contained in:
@@ -1,13 +1,17 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="io.livekit.example">
|
package="io.livekit.example">
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" />
|
<uses-feature android:name="android.hardware.camera" />
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
<application
|
<application
|
||||||
android:label="LiveKit Example"
|
android:label="LiveKit Example"
|
||||||
android:icon="@mipmap/livekit_ic_launcher">
|
android:icon="@mipmap/livekit_ic_launcher">
|
||||||
@@ -40,6 +44,11 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<service
|
||||||
|
android:name="de.julianassmann.flutter_background.IsolateHolderService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="mediaProjection" />
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
|
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_background/flutter_background.dart';
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
|
|
||||||
import '../exts.dart';
|
import '../exts.dart';
|
||||||
@@ -114,7 +115,6 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _shareScreen() async {
|
void _shareScreen() async {
|
||||||
//
|
|
||||||
final lp = widget.room.localParticipant;
|
final lp = widget.room.localParticipant;
|
||||||
|
|
||||||
for (final track in lp.videoTracks) {
|
for (final track in lp.videoTracks) {
|
||||||
@@ -122,6 +122,17 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Required for android screenshare.
|
||||||
|
const androidConfig = FlutterBackgroundAndroidConfig(
|
||||||
|
notificationTitle: 'Screen Sharing',
|
||||||
|
notificationText: 'LiveKit Example is sharing the screen.',
|
||||||
|
notificationImportance: AndroidNotificationImportance.Default,
|
||||||
|
notificationIcon:
|
||||||
|
AndroidResource(name: 'livekit_ic_launcher', defType: 'mipmap'),
|
||||||
|
);
|
||||||
|
await FlutterBackground.initialize(androidConfig: androidConfig);
|
||||||
|
await FlutterBackground.enableBackgroundExecution();
|
||||||
|
|
||||||
final screenTrack =
|
final screenTrack =
|
||||||
await LocalVideoTrack.createScreenTrack(); // Defaults to camera
|
await LocalVideoTrack.createScreenTrack(); // Defaults to camera
|
||||||
await widget.room.localParticipant.publishVideoTrack(
|
await widget.room.localParticipant.publishVideoTrack(
|
||||||
@@ -132,6 +143,17 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _unshareScreen() async {
|
||||||
|
final lp = widget.room.localParticipant;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await lp.setScreenShareEnabled(false);
|
||||||
|
await FlutterBackground.disableBackgroundExecution();
|
||||||
|
} catch (e) {
|
||||||
|
print('error disabling screen share: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onTapDisconnect() async {
|
void _onTapDisconnect() async {
|
||||||
final result = await context.showDisconnectDialog();
|
final result = await context.showDisconnectDialog();
|
||||||
if (result == true) await widget.room.disconnect();
|
if (result == true) await widget.room.disconnect();
|
||||||
@@ -156,8 +178,12 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
// mute audio
|
// mute audio
|
||||||
final canMute = participant.hasAudio && !participant.isMuted;
|
final canMute = participant.hasAudio && !participant.isMuted;
|
||||||
|
|
||||||
final videoPub = participant.videoTracks.firstOrNull;
|
final videoPub =
|
||||||
|
participant.getTrackPublicationBySource(TrackSource.camera);
|
||||||
final videoEnabled = videoPub != null && !videoPub.muted;
|
final videoEnabled = videoPub != null && !videoPub.muted;
|
||||||
|
final screenSharePub =
|
||||||
|
participant.getTrackPublicationBySource(TrackSource.screenShareVideo);
|
||||||
|
final screenShareEnabled = screenSharePub != null && !screenSharePub.muted;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -205,11 +231,18 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
onPressed: () => _toggleCamera(),
|
onPressed: () => _toggleCamera(),
|
||||||
tooltip: 'toggle camera',
|
tooltip: 'toggle camera',
|
||||||
),
|
),
|
||||||
IconButton(
|
if (screenShareEnabled)
|
||||||
icon: const Icon(EvaIcons.monitor),
|
IconButton(
|
||||||
onPressed: () => _shareScreen(),
|
icon: const Icon(EvaIcons.monitorOutline),
|
||||||
tooltip: 'share screen (experimental)',
|
onPressed: () => _unshareScreen(),
|
||||||
),
|
tooltip: 'unshare screen (experimental)',
|
||||||
|
)
|
||||||
|
else
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(EvaIcons.monitor),
|
||||||
|
onPressed: () => _shareScreen(),
|
||||||
|
tooltip: 'share screen (experimental)',
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: _onTapDisconnect,
|
onPressed: _onTapDisconnect,
|
||||||
icon: const Icon(EvaIcons.closeCircle),
|
icon: const Icon(EvaIcons.closeCircle),
|
||||||
|
|||||||
+26
-5
@@ -90,6 +90,13 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_background:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_background
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2+2"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -218,6 +225,20 @@ packages:
|
|||||||
name: path_provider
|
name: path_provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
|
version: "2.0.7"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.6"
|
||||||
|
path_provider_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_ios
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
version: "2.0.6"
|
version: "2.0.6"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
@@ -225,7 +246,7 @@ packages:
|
|||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
path_provider_macos:
|
path_provider_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -246,7 +267,7 @@ packages:
|
|||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "2.0.4"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -302,7 +323,7 @@ packages:
|
|||||||
name: shared_preferences_linux
|
name: shared_preferences_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
shared_preferences_macos:
|
shared_preferences_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -330,7 +351,7 @@ packages:
|
|||||||
name: shared_preferences_windows
|
name: shared_preferences_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -419,7 +440,7 @@ packages:
|
|||||||
name: win32
|
name: win32
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.10"
|
version: "2.3.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
flutter_background: 1.0.2+2
|
||||||
provider: ^6.0.1
|
provider: ^6.0.1
|
||||||
logging: ^1.0.1
|
logging: ^1.0.1
|
||||||
shared_preferences: ^2.0.7
|
shared_preferences: ^2.0.7
|
||||||
|
|||||||
@@ -242,6 +242,10 @@ extension LocalParticipantTrackSourceExt on LocalParticipant {
|
|||||||
return setSourceEnabled(TrackSource.microphone, enabled);
|
return setSourceEnabled(TrackSource.microphone, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setScreenShareEnabled(bool enabled) async {
|
||||||
|
return setSourceEnabled(TrackSource.screenShareVideo, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> setSourceEnabled(TrackSource source, bool enabled) async {
|
Future<void> setSourceEnabled(TrackSource source, bool enabled) async {
|
||||||
final pub = getTrackPublicationBySource(source);
|
final pub = getTrackPublicationBySource(source);
|
||||||
if (pub != null) {
|
if (pub != null) {
|
||||||
@@ -261,8 +265,10 @@ extension LocalParticipantTrackSourceExt on LocalParticipant {
|
|||||||
} else if (source == TrackSource.microphone) {
|
} else if (source == TrackSource.microphone) {
|
||||||
final track = await LocalAudioTrack.create();
|
final track = await LocalAudioTrack.create();
|
||||||
await publishAudioTrack(track);
|
await publishAudioTrack(track);
|
||||||
|
} else if (source == TrackSource.screenShareVideo) {
|
||||||
|
final track = await LocalVideoTrack.createScreenTrack();
|
||||||
|
await publishVideoTrack(track);
|
||||||
}
|
}
|
||||||
// TODO: Screen share
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ class LocalVideoTrack extends VideoTrack {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a LocalVideoTrack from the display.
|
||||||
|
///
|
||||||
|
/// Note: Android requires a foreground service to be started prior to
|
||||||
|
/// creating a screen track. Refer to the example app for an implementation.
|
||||||
static Future<LocalVideoTrack> createScreenTrack([
|
static Future<LocalVideoTrack> createScreenTrack([
|
||||||
ScreenTrackOptions? options,
|
ScreenTrackOptions? options,
|
||||||
]) async {
|
]) async {
|
||||||
|
|||||||
Reference in New Issue
Block a user