Isvisoft / flutter_screen_recording

A new Flutter plugin for record the screen.
MIT License
142 stars 138 forks source link

Can you add get file as byte and don't autosave file feature? #93

Open SittiphanSittisak opened 1 year ago

SittiphanSittisak commented 1 year ago

When stopping the recording by using

await FlutterScreenRecording.stopRecordScreen

This package will autosave the file and return with the path as String type.

But I want only to get the file as Blob or byte(Uint8List or List) without autosaving the file to upload to the server.

For example, I edited the flutter_screen_recording_web.dart file like this.

import 'dart:async';
import 'package:universal_html/html.dart' as html;
import 'package:universal_html/js.dart' as js;
import 'package:flutter_test_recording/flutter_screen_recording_platform_interface.dart';
import 'get_display_media.dart';

class WebFlutterScreenRecording extends FlutterScreenRecordingPlatform {
  html.MediaStream? stream;
  String? name;
  html.MediaRecorder? mediaRecorder;
  html.Blob? recordedChunks;
  String? mimeType;
  bool isAutoSaveFile = true;

  @override
  Future<bool> startRecordScreen(String name, {bool isAutoSaveFile = true, Function()? onStopSharing}) async {
    return _record(name, true, false, isAutoSaveFile: isAutoSaveFile, onStopSharing: onStopSharing);
  }

  @override
  Future<bool> startRecordScreenAndAudio(String name, {bool isAutoSaveFile = true, Function()? onStopSharing}) async {
    return _record(name, true, true, isAutoSaveFile: isAutoSaveFile, onStopSharing: onStopSharing);
  }

  Future<bool> _record(String name, bool recordVideo, bool recordAudio, {required bool isAutoSaveFile, Function()? onStopSharing}) async {
    try {
      this.isAutoSaveFile = isAutoSaveFile;
      html.MediaStream? audioStream;

      if (recordAudio) {
        audioStream = await Navigator.getUserMedia({"audio": true});
      }
      stream = await Navigator.getDisplayMedia({"audio": recordAudio, "video": recordVideo});
      this.name = name;
      if (recordAudio) {
        stream!.addTrack(audioStream!.getAudioTracks()[0]);
      }

      if (html.MediaRecorder.isTypeSupported('video/mp4;codecs=h264')) {
        print('video/mp4;codecs=h264');
        mimeType = 'video/mp4;codecs=h264';
      } else if (html.MediaRecorder.isTypeSupported('video/webm;codecs=vp9')) {
        print('video/webm;codecs=vp9');
        mimeType = 'video/webm;codecs=vp9,opus';
      } else if (html.MediaRecorder.isTypeSupported('video/webm;codecs=vp8.0')) {
        print('video/webm;codecs=vp8.0');
        mimeType = 'video/webm;codecs=vp8.0,opus';
      } else if (html.MediaRecorder.isTypeSupported('video/webm;codecs=vp8')) {
        print('video/webm;codecs=vp8');
        mimeType = 'video/webm;codecs=vp8,opus';
      } else if (html.MediaRecorder.isTypeSupported('video/mp4;codecs=h265')) {
        mimeType = 'video/mp4;codecs=h265,opus';
        print("video/mp4;codecs=h265");
      } else if (html.MediaRecorder.isTypeSupported('video/mp4;codecs=h264')) {
        print("video/mp4;codecs=h264");
        mimeType = 'video/mp4;codecs=h264,opus';
      } else if (html.MediaRecorder.isTypeSupported('video/webm;codecs=h265')) {
        print("video/webm;codecs=h265");
        mimeType = 'video/webm;codecs=h265,opus';
      } else if (html.MediaRecorder.isTypeSupported('video/webm;codecs=h264')) {
        print("video/webm;codecs=h264");
        mimeType = 'video/webm;codecs=h264,opus';
      } else {
        mimeType = 'video/webm';
      }

      mediaRecorder = html.MediaRecorder(stream!, {'mimeType': mimeType});

      mediaRecorder!.addEventListener('dataavailable', (html.Event event) {
        print("datavailable ${event.runtimeType}");
        recordedChunks = js.JsObject.fromBrowserObject(event)['data'];
        mimeType = mimeType;
        print("blob size: ${recordedChunks?.size ?? 'empty'}");
      });

      stream!.getVideoTracks()[0].addEventListener('ended', (html.Event event) {
        //If user stop sharing screen, stop record
        stopRecordScreen.then((value) => onStopSharing?.call());
      });

      mediaRecorder!.start();

      return true;
    } on Error catch (e) {
      print("--->$e");
      return false;
    }
  }

  @override
  Future<String> get stopRecordScreen {
    final c = Completer<String>();
    mediaRecorder!.addEventListener("stop", (event) async {
      mediaRecorder = null;
      stream!.getTracks().forEach((element) => element.stop());
      stream = null;
      if (isAutoSaveFile) {
        final a = html.document.createElement("a") as html.AnchorElement;
        final url = html.Url.createObjectUrl(blobFile);
        html.document.body!.append(a);
        a.style.display = "none";
        a.href = url;
        a.download = name;
        a.click();
        html.Url.revokeObjectUrl(url);
        c.complete(name);
      } else {
        c.complete('');
      }
    });
    mediaRecorder!.stop();
    return c.future;
  }

  html.Blob? get blobFile {
    try {
      return html.Blob(List<dynamic>.from([recordedChunks]), mimeType);
    } catch (_) {
      return null;
    }
  }
}

and using like.

import 'package:flutter/material.dart';
import 'package:flutter_meedu_videoplayer/meedu_player.dart';
import 'package:flutter_test_recording/flutter_screen_recording_web.dart';
import 'package:quiver/async.dart';
import 'package:url_strategy/url_strategy.dart';
import 'package:universal_html/html.dart' as html;

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  setPathUrlStrategy();
  initMeeduPlayer();
  runApp(const MyApp());
}

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

  @override
  // ignore: library_private_types_in_public_api
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late final controller = MeeduPlayerController();
  final flutterScreenRecording = WebFlutterScreenRecording();
  bool recording = false;
  int _time = 0;
  html.Blob? blob;

  requestPermissions() async {}

  @override
  void initState() {
    super.initState();
    requestPermissions();
    startTimer();
  }

  void startTimer() {
    CountdownTimer countDownTimer = CountdownTimer(
      const Duration(seconds: 1000),
      const Duration(seconds: 1),
    );

    var sub = countDownTimer.listen(null);
    sub.onData((duration) {
      setState(() => _time++);
    });

    sub.onDone(() {
      print("Done");
      sub.cancel();
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      themeMode: ThemeMode.dark,
      darkTheme: ThemeData.dark(),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Screen Recording'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            if (blob != null) ...[
              SizedBox(
                width: 800,
                height: 450,
                child: AspectRatio(
                  aspectRatio: 16 / 9,
                  child: MeeduVideoPlayer(controller: controller),
                ),
              )
            ],
            Text('Time: $_time\n'),
            !recording
                ? Center(
                    child: ElevatedButton(
                      child: const Text("Record Screen"),
                      onPressed: () => startScreenRecord(false),
                    ),
                  )
                : Container(),
            !recording
                ? Center(
                    child: ElevatedButton(
                      child: const Text("Record Screen & audio"),
                      onPressed: () => startScreenRecord(true),
                    ),
                  )
                : Center(
                    child: ElevatedButton(
                      child: const Text("Stop Record"),
                      onPressed: () => stopScreenRecord(),
                    ),
                  )
          ],
        ),
      ),
    );
  }

  getFileAndDisplay() {
    blob = flutterScreenRecording.blobFile;
    if (blob == null) return;
    final url = html.Url.createObjectUrlFromBlob(blob!);
    controller.setDataSource(
      DataSource(type: DataSourceType.network, source: url),
      autoplay: true,
    );
    setState(() {
      recording = !recording;
    });
  }

  startScreenRecord(bool audio) async {
    final start = audio ? await flutterScreenRecording.startRecordScreenAndAudio("Title", isAutoSaveFile: false, onStopSharing: getFileAndDisplay) : await flutterScreenRecording.startRecordScreen("Title", isAutoSaveFile: false, onStopSharing: getFileAndDisplay);
    if (start) setState(() => recording = !recording);
    return start;
  }

  stopScreenRecord() async {
    final path = await flutterScreenRecording.stopRecordScreen;
    getFileAndDisplay();
  }
}

I added bool isAutoSaveFile and Function()? onStopSharing parameters for startRecordScreen and startRecordScreenAndAudio to use in _record and I added html.Blob? get blobFile to get the Blob file. (I think should have getMimeType too)

The Blob doesn't work on Android and iOS devices because it uses HTML. Then return as bytes is better for supporting all platforms.

Can you add this feature?

chenqinggang001 commented 1 month ago

+1