Skip to content

Latest commit

 

History

History
246 lines (182 loc) · 6.48 KB

README.md

File metadata and controls

246 lines (182 loc) · 6.48 KB

screen_streamer

Table of Contents


Introduction

A package that utilizes the super awesome flutter_webrtc package to be able to send the screen from one device to another. The package was inspired by a closed Flutter Issue.

This is compatible with all Flutter platforms, including Web, but has only been tested on Android, iOS, MacOS, and Web.

This package provides two examples. One to send the screen and the other to receive the screen.

As a note, this library will not work on simulators or emulators as the underlying frameworks don't exist. You must use a physical device when using this framework.


Setup

Android

On Android, you must add three permissions:

  1. android.permission.ACCESS_NETWORK_STATE
  2. android.permission.FOREGROUND_SERVICE
  3. android.permission.SCHEDULE_EXACT_ALARM

Next, within the application tag, you must add the following service:

<service android:name="id.flutter.flutter_background_service.BackgroundService"
    android:foregroundServiceType="mediaProjection"
    android:enabled="true"
    android:exported="false"
    tools:replace="android:exported" />

Your AndroidManifest.xml file will now look similar to:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.sender">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

    <application
        android:label="sender"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme"
            />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to
        generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />

        <service android:name="id.flutter.flutter_background_service.BackgroundService"
            android:foregroundServiceType="mediaProjection"
            android:enabled="true"
            android:exported="false"
            tools:replace="android:exported" />

    </application>
</manifest>

iOS

No special configuration needed for iOS


MacOS

Open your macOS workspace in Xcode and enable both of the network permissions fro all build modes:

  • Incoming Connections (Server)
  • Outgoing Connections (Client)

Linux

No known special configuration needed for Linux


Web

No known special configuration needed for Web


Windows

No known special configuration needed for Windows


Usage

Sending

To send a screen to a remote listener, you can utilize the ScreenSender class.

final sender = ScreenSender();
await sender.connect(
  Uri.parse(_controller.text),
  context: context,
);

Receiving

To send a screen to a remote listener, you can utilize the ScreenReceiver class along with the RemoteScreenRenderer.

import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:screen_streamer/screen_streamer.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Receiver',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    super.key,
  });

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ScreenReceiver _receiver = ScreenReceiver();

  bool _connected = false;

  @override
  void initState() {
    super.initState();

    _receiver.listen().then(((value) {
      _connected = true;
      if (mounted) {
        setState(() {});
      }
    }));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_connected ? 'Connected' : 'Waiting'),
      ),
      body: _connected
          ? RemoteScreenRenderer(receiver: _receiver)
          : Center(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                mainAxisSize: MainAxisSize.max,
                children: [
                  const CircularProgressIndicator(),
                  FutureBuilder(
                    builder: (context, snapshot) => snapshot.hasData
                        ? Padding(
                            padding: const EdgeInsets.only(top: 8.0),
                            child: Text(snapshot.data.toString()),
                          )
                        : const SizedBox(),
                    future: _receiver.uri,
                  ),
                ],
              ),
            ),
    );
  }
}