Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
SittiphanSittisak opened this issue Aug 20, 2023 · 1 comment
Open

Comments

@SittiphanSittisak
Copy link

SittiphanSittisak commented Aug 20, 2023

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
Copy link

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants