Skip to content

Commit

Permalink
Record settings from dialog are saved to app settings and stored in a…
Browse files Browse the repository at this point in the history
… YAML file.
  • Loading branch information
larryaasen committed Sep 4, 2023
1 parent b0e9cf3 commit 5ceee03
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 101 deletions.
36 changes: 12 additions & 24 deletions packages/dive/lib/src/dive_recording_output.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,8 @@ class DiveRecordingOutput {
return const DiveOutputRecordingState();
}, name: 'output-recording-provider');

DiveOutputRecordingState get state =>
DiveCore.container.read(provider.notifier).state;
set state(DiveOutputRecordingState newState) =>
DiveCore.container.read(provider.notifier).state = newState;
DiveOutputRecordingState get state => DiveCore.container.read(provider.notifier).state;
set state(DiveOutputRecordingState newState) => DiveCore.container.read(provider.notifier).state = newState;

DivePointerOutput? _output;
Timer? _updateTimer;
Expand All @@ -64,7 +62,7 @@ class DiveRecordingOutput {
stop();
}

void updateFromMap(Map<String, Object> map) {
void updateFromMap(Map<String, Object?> map) {
final newState = state.copyWith(
folder: map['folder'] as String? ?? '',
);
Expand All @@ -80,27 +78,23 @@ class DiveRecordingOutput {
/// Start recording locally at the [filePath] specified.
/// "/Users/larry/Movies/dive1.mkv"
/// when [appendTimeStamp] is true:
bool start(String filePath,
{String? filename,
bool appendTimeStamp = false,
String extension = 'mkv'}) {
bool start({String? filename, bool appendTimeStamp = false, String extension = 'mkv'}) {
if (_output != null) {
stop();
}

String outputPath = filePath;
String outputPath = state.folder ?? '';
if (filename != null && appendTimeStamp) {
final now = DateTime.now();
final date = DiveFormat.formatterRecordingDate.format(now);
final time = DiveFormat.formatterRecordingTime.format(now);
final timeFilename = '$filename $date at $time.$extension';
outputPath = path.join(filePath, timeFilename);
outputPath = path.join(outputPath, timeFilename);
}
DiveSystemLog.message('DiveRecordingOutput.start at path: $outputPath');

// Create recording service
_output = obslib.recordingOutputCreate(
path: outputPath, outputName: 'tbd', outputType: outputType);
_output = obslib.recordingOutputCreate(path: outputPath, outputName: 'tbd', outputType: outputType);
if (_output == null) {
DiveSystemLog.error('DiveRecordingOutput.start output create failed');
return false;
Expand All @@ -110,8 +104,7 @@ class DiveRecordingOutput {
final rv = obslib.outputStart(_output!);
if (rv) {
_updateState();
_updateTimer =
Timer.periodic(const Duration(seconds: 1), (timer) => _updateState());
_updateTimer = Timer.periodic(const Duration(seconds: 1), (timer) => _updateState());
} else {
DiveSystemLog.error('DiveRecordingOutput.start output start failed');
}
Expand Down Expand Up @@ -145,18 +138,13 @@ class DiveRecordingOutput {
/// Sync the media state from the media source to the state provider.
Future<void> _updateState() async {
if (_output == null) return;
final activeState =
DiveOutputRecordingActiveState.values[obslib.outputGetState(_output!)];
final activeState = DiveOutputRecordingActiveState.values[obslib.outputGetState(_output!)];
final currentState = state;
var startTime = currentState.startTime;
if (currentState.startTime == null &&
activeState == DiveOutputRecordingActiveState.active) {
if (currentState.startTime == null && activeState == DiveOutputRecordingActiveState.active) {
startTime = DateTime.now();
}
final duration = startTime != null
? DateTime.now().difference(startTime)
: Duration.zero;
state = DiveOutputRecordingState(
activeState: activeState, startTime: startTime, duration: duration);
final duration = startTime != null ? DateTime.now().difference(startTime) : Duration.zero;
state = DiveOutputRecordingState(activeState: activeState, startTime: startTime, duration: duration);
}
}
7 changes: 7 additions & 0 deletions packages/dive_ui/example/lib/dive_caster.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:dive/dive.dart';
import 'package:dive_ui/dive_caster.dart';
import 'package:dive_ui/dive_ui_widgets.dart';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';

/// Dive Caster Multi Camera Streaming and Recording
Expand Down Expand Up @@ -72,6 +73,12 @@ class DiveCasterMain {

// Create the recording output
final recordingOutput = DiveRecordingOutput();
recordingOutput.updateFromMap(elementsNode.settings['recording'] as Map<String, dynamic>? ?? {});
final recordingState = recordingOutput.state;
if (recordingState.folder == null || recordingState.folder!.isEmpty) {
final folder = path.join(applicationSupportDirectory.path, 'recordings');
recordingOutput.state = recordingState.copyWith(folder: folder);
}
elements.addRecordingOutput(recordingOutput);

// Create the streaming output
Expand Down
3 changes: 2 additions & 1 deletion packages/dive_ui/example/lib/main_example15.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,10 @@ class _BodyWidgetState extends State<BodyWidget> {
// Create the recording output
final recordingOutput = DiveRecordingOutput();
widget.elements.addRecordingOutput(recordingOutput);
recordingOutput.state = recordingOutput.state.copyWith(folder: '/Users/larry/Movies/dive/');

// Start recording.
recordingOutput.start('/Users/larry/Movies/dive/dive1.mkv');
recordingOutput.start(filename: 'dive1', appendTimeStamp: true);
}

@override
Expand Down
116 changes: 45 additions & 71 deletions packages/dive_ui/lib/dive_caster.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import 'package:dive/dive.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path/path.dart' as path;

import 'dive_record_settings_dialog.dart';
import 'dive_stream_settings_dialog.dart';
Expand Down Expand Up @@ -118,13 +117,10 @@ class DiveHeaderRecordButton extends StatelessWidget {
String duration = '';
final elementsState = ref.watch(elements.provider);
if (elementsState.recordingOutput != null) {
final recordingState =
ref.watch(elementsState.recordingOutput!.provider);
recording = recordingState.activeState ==
DiveOutputRecordingActiveState.active;
duration = recordingState.duration != null
? DiveFormat.formatDuration(recordingState.duration!)
: '';
final recordingState = ref.watch(elementsState.recordingOutput!.provider);
recording = recordingState.activeState == DiveOutputRecordingActiveState.active;
duration =
recordingState.duration != null ? DiveFormat.formatDuration(recordingState.duration!) : '';
}
return DiveHeaderButton(
title: recording ? 'RECORDING' : 'RECORD',
Expand All @@ -133,18 +129,14 @@ class DiveHeaderRecordButton extends StatelessWidget {
onPressed: () async {
final elementsState = elements.state;
if (elementsState.recordingOutput != null) {
final recordingState =
ref.read(elementsState.recordingOutput!.provider);
final recording = recordingState.activeState ==
DiveOutputRecordingActiveState.active;
final recordingState = ref.read(elementsState.recordingOutput!.provider);
final recording = recordingState.activeState == DiveOutputRecordingActiveState.active;
if (recording) {
// Stop recording.
elementsState.recordingOutput!.stop();
} else {
final folder = path.join('~/Documents');
// Start recording.
elementsState.recordingOutput!
.start(folder, filename: 'dive1', appendTimeStamp: true);
elementsState.recordingOutput!.start(filename: 'dive1', appendTimeStamp: true);
}
}
},
Expand All @@ -166,8 +158,7 @@ class DiveHeaderRecordButton extends StatelessWidget {
child: DiveRecordSettingsScreen(
saveFolder: recordingOutput.state.folder,
useDialog: true,
onApplyCallback: (String directory) =>
_onDialogApply(context, directory),
onApplyCallback: (String directory) => _onDialogApply(context, directory),
),
);
});
Expand All @@ -177,7 +168,10 @@ class DiveHeaderRecordButton extends StatelessWidget {
final recordingOutput = elements.state.recordingOutput;
if (recordingOutput != null) {
recordingOutput.stop();
// ? recordingOutput. .state.folder = directory;
recordingOutput.state = recordingOutput.state.copyWith(folder: directory);

// Save the updated settings.
elements.saveAppSettings();
}
Navigator.of(context).pop();
}
Expand All @@ -197,15 +191,11 @@ class DiveHeaderStreamButton extends StatelessWidget {
String duration = '';
final elementsState = ref.watch(elements.provider);
if (elementsState.streamingOutput != null) {
final streamingState =
ref.watch(elementsState.streamingOutput!.provider);
streaming = streamingState.activeState ==
DiveOutputStreamingActiveState.active;
failed = streamingState.activeState ==
DiveOutputStreamingActiveState.failed;
duration = streamingState.duration != null
? DiveFormat.formatDuration(streamingState.duration!)
: '';
final streamingState = ref.watch(elementsState.streamingOutput!.provider);
streaming = streamingState.activeState == DiveOutputStreamingActiveState.active;
failed = streamingState.activeState == DiveOutputStreamingActiveState.failed;
duration =
streamingState.duration != null ? DiveFormat.formatDuration(streamingState.duration!) : '';
}
return DiveHeaderButton(
title: streaming ? 'STREAMING' : 'STREAM',
Expand All @@ -220,10 +210,8 @@ class DiveHeaderStreamButton extends StatelessWidget {
onPressed: () {
final elementsState = elements.state;
if (elementsState.streamingOutput != null) {
final recordingState =
ref.read(elementsState.streamingOutput!.provider);
final active = recordingState.activeState ==
DiveOutputStreamingActiveState.active;
final recordingState = ref.read(elementsState.streamingOutput!.provider);
final active = recordingState.activeState == DiveOutputStreamingActiveState.active;
if (active) {
// Stop streaming.
elementsState.streamingOutput!.stop();
Expand All @@ -248,34 +236,30 @@ class DiveHeaderStreamButton extends StatelessWidget {
builder: (BuildContext context) {
return SingleChildScrollView(
child: DiveStreamSettingsScreen(
service: elements.state.streamingOutput == null
? null
: elements.state.streamingOutput!.service,
server: elements.state.streamingOutput == null
? null
: elements.state.streamingOutput!.server,
serviceKey: elements.state.streamingOutput == null
? null
: elements.state.streamingOutput!.serviceKey,
service:
elements.state.streamingOutput == null ? null : elements.state.streamingOutput!.service,
server: elements.state.streamingOutput == null ? null : elements.state.streamingOutput!.server,
serviceKey:
elements.state.streamingOutput == null ? null : elements.state.streamingOutput!.serviceKey,
useDialog: true,
onApplyCallback: (DiveRTMPService service, DiveRTMPServer server,
String serviceKey) =>
onApplyCallback: (DiveRTMPService service, DiveRTMPServer server, String serviceKey) =>
_onDialogApply(context, service, server, serviceKey),
),
);
});
}

void _onDialogApply(BuildContext context, DiveRTMPService service,
DiveRTMPServer server, String serviceKey) {
final state = elements.state;
if (state.streamingOutput != null) {
state.streamingOutput!.stop();
state.streamingOutput!.service = service;
state.streamingOutput!.server = server;
state.streamingOutput!.serviceUrl = server.url;
state.streamingOutput!.serviceKey = serviceKey;

void _onDialogApply(
BuildContext context, DiveRTMPService service, DiveRTMPServer server, String serviceKey) {
final streamingOutput = elements.state.streamingOutput;
if (streamingOutput != null) {
streamingOutput.stop();
streamingOutput.service = service;
streamingOutput.server = server;
streamingOutput.serviceUrl = server.url;
streamingOutput.serviceKey = serviceKey;

// Save the updated settings.
elements.saveAppSettings();
}
Navigator.of(context).pop();
Expand All @@ -295,8 +279,7 @@ class DiveCasterFooter extends StatelessWidget {
height: 40.0,
child: Row(
children: [
DiveHeaderIcon(
icon: Icon(Icons.live_tv, color: DiveCasterTheme.textColor)),
DiveHeaderIcon(icon: Icon(Icons.live_tv, color: DiveCasterTheme.textColor)),
DiveHeaderText(text: 'Dive Caster'),
],
),
Expand Down Expand Up @@ -358,13 +341,10 @@ class DiveCasterContentArea extends StatelessWidget {
return Consumer(
builder: (context, ref, child) {
final elementsState = ref.watch(elements.provider);
final controller = elementsState.videoMixes.isNotEmpty
? elementsState.videoMixes.first.controller
: null;
final controller =
elementsState.videoMixes.isNotEmpty ? elementsState.videoMixes.first.controller : null;
return Center(
child: DivePreview(
aspectRatio: DiveCoreAspectRatio.HD.ratio,
controller: controller),
child: DivePreview(aspectRatio: DiveCoreAspectRatio.HD.ratio, controller: controller),
);
},
);
Expand All @@ -388,8 +368,7 @@ class DiveHeaderClock extends ConsumerWidget {
width: 100.0,
height: 36,
child: Center(
child: Text(nowFormatted,
style: TextStyle(color: DiveCasterTheme.textColor)),
child: Text(nowFormatted, style: TextStyle(color: DiveCasterTheme.textColor)),
),
);
}
Expand Down Expand Up @@ -467,15 +446,12 @@ class _DiveHeaderButtonState extends State<DiveHeaderButton> {
: widget.useRedBackground
? MaterialStatePropertyAll(DiveCasterTheme.headerButtonRedColor)
: MaterialStatePropertyAll(DiveCasterTheme.headerBackgroundColor),
foregroundColor:
MaterialStatePropertyAll(DiveCasterTheme.headerButtonTextColor),
foregroundColor: MaterialStatePropertyAll(DiveCasterTheme.headerButtonTextColor),
overlayColor: widget.useBlueBackground
? MaterialStatePropertyAll(DiveCasterTheme.headerButtonBlueHoverColor)
: widget.useRedBackground
? MaterialStatePropertyAll(
DiveCasterTheme.headerButtonRedHoverColor)
: MaterialStatePropertyAll(
DiveCasterTheme.headerButtonHoverColor),
? MaterialStatePropertyAll(DiveCasterTheme.headerButtonRedHoverColor)
: MaterialStatePropertyAll(DiveCasterTheme.headerButtonHoverColor),
splashFactory: NoSplash.splashFactory,
);

Expand All @@ -499,9 +475,7 @@ class _DiveHeaderButtonState extends State<DiveHeaderButton> {
if (widget.onGearPressed != null)
IconButton(
icon: Icon(DiveUI.iconSet.sourceSettingsButton),
color: _hovering
? DiveCasterTheme.headerButtonTextColor
: DiveCasterTheme.headerButtonHoverColor,
color: _hovering ? DiveCasterTheme.headerButtonTextColor : DiveCasterTheme.headerButtonHoverColor,
onPressed: widget.onGearPressed,
),
],
Expand Down
15 changes: 13 additions & 2 deletions packages/dive_ui/lib/dive_record_settings_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) 2023 Larry Aasen. All rights reserved.

import 'package:dive/dive.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';

import 'dive_ui.dart';

/// An icon button that presents the record settings dialog.
Expand Down Expand Up @@ -96,13 +98,14 @@ class _DiveRecordSettingsScreenState extends State<DiveRecordSettingsScreen> {

Widget _buildConfig(BuildContext context) {
final position = Padding(
padding: EdgeInsets.only(top: 20, left: 10, right: 10),
padding: EdgeInsets.only(top: 0.0, left: 10, right: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ElevatedButton(child: Text('Change Folder'), onPressed: () => _onChangeFolder()),
Padding(padding: EdgeInsets.only(top: 20), child: Text('Save recordings to this folder:')),
Text(_saveFolder),
Container(constraints: BoxConstraints(maxWidth: 400.0), child: Text(_saveFolder)),
],
));

Expand All @@ -127,6 +130,14 @@ class _DiveRecordSettingsScreenState extends State<DiveRecordSettingsScreen> {
_saveFolder = widget.saveFolder ?? '';
}

void _onChangeFolder() async {
getDirectoryPath(confirmButtonText: 'OK', initialDirectory: '').then((String? path) {
if (path == null) return;
DiveSystemLog.message('DiveRecordSettingsScreen: patj=$path', group: 'dive_ui');
setState(() => _saveFolder = path);
});
}

void _onReset() {
setState(() {
_useInitialState();
Expand Down
3 changes: 0 additions & 3 deletions packages/dive_ui/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ dependencies:
# From Dart team:
path_provider: ^2.0.13

# From flutter.dev: Flutter plugin for playing back video on a Widget surface.
# video_player: '>=0.10.12+3 <2.0.0'

dev_dependencies:
flutter_test:
sdk: flutter
Expand Down

0 comments on commit 5ceee03

Please sign in to comment.