mirror of
https://github.com/NaserElziadna/doddle.git
synced 2026-03-13 07:41:06 +08:00
new recording idea
This commit is contained in:
@@ -80,12 +80,7 @@ class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
drawController = context.read<DoddlerBloc>().drawController;
|
||||
timer = Timer.periodic(Duration(milliseconds: 34), (Timer t) {
|
||||
context
|
||||
.read<RecorderBloc>()
|
||||
.add(TakeSnapshotEvent(globalKey: Doddler.globalKey));
|
||||
print("TakeSnapshotEvent");
|
||||
});
|
||||
context.read<RecorderBloc>().add(StartRecordingEvent());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'frame.dart';
|
||||
import 'package:image/image.dart' as image;
|
||||
|
||||
class RecorderController {
|
||||
final GlobalKey? globalKey;
|
||||
@@ -11,6 +17,44 @@ class RecorderController {
|
||||
this.frames,
|
||||
});
|
||||
|
||||
Future<Uint8List> convertImageToUint8List(ui.Image image) async {
|
||||
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
Uint8List pngBytes = byteData!.buffer.asUint8List();
|
||||
return pngBytes;
|
||||
}
|
||||
|
||||
Future<List<int>?> export() async {
|
||||
List<RawFrame> bytes = [];
|
||||
for (final frame in frames!) {
|
||||
final i = await frame.frame!.toByteData(format: ui.ImageByteFormat.png);
|
||||
if (i != null) {
|
||||
bytes.add(RawFrame(32, i));
|
||||
} else {
|
||||
print('Skipped frame while enconding');
|
||||
}
|
||||
}
|
||||
final result = compute(_export, bytes);
|
||||
frames!.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
static Future<List<int>?> _export(List<RawFrame> frames) async {
|
||||
final animation = image.Animation();
|
||||
animation.backgroundColor = Colors.transparent.value;
|
||||
for (final frame in frames) {
|
||||
final iAsBytes = frame.image.buffer.asUint8List();
|
||||
final decodedImage = image.decodePng(iAsBytes);
|
||||
|
||||
if (decodedImage == null) {
|
||||
print('Skipped frame while enconding');
|
||||
continue;
|
||||
}
|
||||
decodedImage.duration = frame.durationInMillis;
|
||||
animation.addFrame(decodedImage);
|
||||
}
|
||||
return image.encodeGifAnimation(animation);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
@@ -37,3 +81,10 @@ class RecorderController {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RawFrame {
|
||||
RawFrame(this.durationInMillis, this.image);
|
||||
|
||||
final int durationInMillis;
|
||||
final ByteData image;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
@@ -22,53 +23,75 @@ class MovieTimePageState extends State<MovieTimePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => RecorderBloc(),
|
||||
child: Scaffold(
|
||||
body: Center(
|
||||
child: BlocBuilder<RecorderBloc, RecorderState>(
|
||||
builder: (context, state) {
|
||||
if (state is NextFrameState) {
|
||||
return FutureBuilder(
|
||||
future: convertImageToUint8List(state.frame!.frame!),
|
||||
builder: (BuildContext context,
|
||||
AsyncSnapshot<Uint8List> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: Text('Please wait its loading...'));
|
||||
} else {
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
} else {
|
||||
context.read<RecorderBloc>().add(
|
||||
CallNextFrameEvent(index: state.frame!.index! + 1));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<RecorderBloc>().add(SaveGifEvent());
|
||||
},
|
||||
icon: Icon(Icons.not_started))
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: BlocBuilder<RecorderBloc, RecorderState>(
|
||||
builder: (context, state) {
|
||||
if (state is NextFrameState) {
|
||||
Future.delayed(Duration(seconds: 1));
|
||||
|
||||
return Center(
|
||||
child: Image.memory(
|
||||
snapshot.data!,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
),
|
||||
);
|
||||
} // snapshot.data :- get your object which is pass from your downloadData() function
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if (state is InitialRecorderState) {
|
||||
context.read<RecorderBloc>().add(CallNextFrameEvent(index: 0));
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
return const CircularProgressIndicator(
|
||||
color: Colors.red,
|
||||
return FutureBuilder(
|
||||
future: convertImageToUint8List(state.frame!.frame!),
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: Text('Please wait its loading...'));
|
||||
} else {
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
} else {
|
||||
return Center(
|
||||
child: Image.memory(
|
||||
snapshot.data!,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
),
|
||||
);
|
||||
} // snapshot.data :- get your object which is pass from your downloadData() function
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
} else if (state is InitialRecorderState) {
|
||||
// context.read<RecorderBloc>().add(CallNextFrameEvent(index: 0));
|
||||
return const CircularProgressIndicator();
|
||||
} else if (state is MessageState) {
|
||||
return LinearProgressIndicator(
|
||||
semanticsLabel: state.message,
|
||||
);
|
||||
} else if (state is ShowGifState) {
|
||||
return Center(
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
child: Image.memory(
|
||||
Uint8List.fromList(state.gif!),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const CircularProgressIndicator(
|
||||
color: Colors.red,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Uint8List> convertImageToUint8List(ui.Image image) {
|
||||
return Future.value();
|
||||
Future<Uint8List> convertImageToUint8List(ui.Image image) async {
|
||||
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
Uint8List pngBytes = byteData!.buffer.asUint8List();
|
||||
return pngBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
@@ -7,6 +8,7 @@ import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'package:doddle/models/recorder_controller.dart';
|
||||
|
||||
import '../doddler.dart';
|
||||
import 'recorder_event.dart';
|
||||
import 'recorder_state.dart';
|
||||
|
||||
@@ -14,18 +16,34 @@ class RecorderBloc extends Bloc<RecorderEvent, RecorderState> {
|
||||
RecorderController? recorderController =
|
||||
RecorderController(frames: [], globalKey: GlobalKey());
|
||||
int index = 0;
|
||||
bool isRecording = false;
|
||||
int recordingIndex = 0;
|
||||
Timer? timer;
|
||||
|
||||
RecorderBloc({this.recorderController}) : super(InitialRecorderState());
|
||||
|
||||
@override
|
||||
Stream<RecorderState> mapEventToState(RecorderEvent event) async* {
|
||||
if (event is TakeSnapshotEvent) {
|
||||
final image = await canvasToImage(event.globalKey!);
|
||||
final frames = recorderController!.frames;
|
||||
frames!.add(Frame(frame: image, index: index++));
|
||||
recorderController = recorderController!.copyWith(frames: frames);
|
||||
} else if (event is CallNextFrameEvent) {
|
||||
yield NextFrameState(frame: recorderController!.frames![event.index]);
|
||||
if (!isRecording) {
|
||||
timer =
|
||||
Timer.periodic(const Duration(milliseconds: 34), (Timer t) async {
|
||||
final frames = recorderController!.frames;
|
||||
|
||||
final image = await canvasToImage(event.globalKey!);
|
||||
frames!.add(Frame(frame: image, index: index++));
|
||||
recorderController = recorderController!.copyWith(frames: frames);
|
||||
});
|
||||
}
|
||||
} else if (event is SaveGifEvent) {
|
||||
add(StopRecordingEvent());
|
||||
yield MessageState("Waiting ... ");
|
||||
final gif = await recorderController!.export();
|
||||
yield ShowGifState(gif: gif);
|
||||
} else if (event is StartRecordingEvent) {
|
||||
add(TakeSnapshotEvent(globalKey: Doddler.globalKey));
|
||||
} else if (event is StopRecordingEvent) {
|
||||
timer!.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ class PlayVideoEvent extends RecorderEvent {}
|
||||
|
||||
class StopVideoEvent extends RecorderEvent {}
|
||||
|
||||
class SaveGifEvent extends RecorderEvent {}
|
||||
|
||||
class CallNextFrameEvent extends RecorderEvent {
|
||||
final int index;
|
||||
CallNextFrameEvent({
|
||||
|
||||
@@ -10,3 +10,16 @@ class NextFrameState extends RecorderState {
|
||||
this.frame,
|
||||
});
|
||||
}
|
||||
|
||||
class MessageState extends RecorderState {
|
||||
final String message;
|
||||
|
||||
MessageState(this.message);
|
||||
}
|
||||
|
||||
class ShowGifState extends RecorderState {
|
||||
final gif;
|
||||
ShowGifState({
|
||||
this.gif,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class _ToolsWidgetState extends State<ToolsWidget> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const MovieTimePage()),
|
||||
builder: (context) => MovieTimePage()),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user