Skip to main content
GuidesAPI ReferenceExamplesFAQ

Recorder

Estimated reading time: 13 minutes

The Recorder module is a comprehensive tool designed for managing sensor data recording.

The Recorder supports a wide range of configurable parameters through the RecorderConfiguration, allowing developers to tailor recording behavior to specific application needs. Features include:

  • Customizable storage options: Define log directories, manage disk space, and specify the duration of recordings.
  • Data type selection: Specify which data types (e.g., video, audio, or sensor data) to record.
  • Video and audio options: Set video resolution, enable/disable audio recording, and manage chunk durations. It's possible to record only sensor data, sensor data + video, sensor data + audio or sensor data + video + audio.

The Recorder provides comprehensive methods to control the recording lifecycle:

  • Start, stop, pause, and resume recordings.
  • Automatic restarts for continuous recording with chunked durations.

The Recorder supports various transportation modes (e.g., car, pedestrian, bike), enabling detailed analysis and classification of recordings based on context.

To prevent overwhelming the device's storage, the Recorder allows for setting disk space limits and automatically manages logs based on specified retention thresholds.

In the following class diagram you can see the main classes used by the Recorder and the relationships between them: Recorder UML image

Initialize the Recorder

The Recorder cannot be directly instantiated. Instead, the create method is used to obtain a configured Recorder instance.

RecorderConfiguration recorderConfiguration = RecorderConfiguration(
dataSource: dataSource,
logsDir: tracksPath,
recordedTypes: [DataType.position],
);

Recorder recorder = Recorder.create(recorderConfiguration);
info

If the dataSource, logsDir and recordedTypes parameters are not populated with valid data, the startRecording method will return GemError.invalidInput and no data will be recorded.

Configure the RecorderConfiguration

Recorder configuration attributeDescription
dataSourceThe source providing the data to be recorded.
logsDirThe directory used to keep the logs
deviceModelThe device model
recordedTypesThe data types that are recorded
minDurationSecondsThe minimum duration for the recording to be saved
videoQualityThe video quality
chunkDurationSecondsThe chunk duration time in seconds
continuousRecordingWhether the recording should continue automatically when chunk time achieved
enableAudioThis flag will be used to determine if audio is needed to be recorded or not
maxDiskSpaceUsedWhen reached, it will stop the recording
keepMinSecondsWill not delete any record if this threshold is not reached
deleteOlderThanKeepMinOlder logs that exceeds minimum kept seconds threshold should be deleted
transportModeThe transport mode
warning

If the log duration is shorter than minDurationSeconds the stopRecording method of Recorder will not save the recording and will return GemError.recordedLogTooShort.

The GemError.recordedLogTooShort error may also occur if an insufficient number of positions were emitted, even when the duration between startRecording and stopRecording exceeds minDurationSeconds. To test the recording functionality, you can create a custom external DataSource and push custom positions. Refer to the custom positioning guide for more details. The external DataSource created needs to be provided to the RecorderConfiguration object.

The GemError.general result might be returned if the application has been sent to background without making the required configuration. See the record while app is in background section below.

Be aware that if minChunkDuration is set too high, it may cause GemError.noDiskSpace since the SDK will determine how much space is required for the entire chunk.

tip

The path_provider package can be used to obtain a valid path to save recordings. The following snippet shows how to obtain a valid folder path in a platform independent way:

Future<String> getTracksPath() async {
// Requires the path_provider package
final rootDir = await path_provider.getApplicationDocumentsDirectory();
final tracksPath = path.joinAll([rootDir.path, "Data", "Tracks"]);
return tracksPath;
}

Recording lifecycle

  1. Starting the Recorder:

    • Call the startRecording method to initiate recording. The recorder transitions to the recording state.
  2. Pausing and Resuming:

    • Use pauseRecording and resumeRecording to manage interruptions.
  3. Chunked Recordings:

    • If a chunk duration is set in the configuration, the recording automatically stops when the duration is reached. A new recording will begin seamlessly if continuous recording is enabled, ensuring uninterrupted data capture.
  4. Stopping the Recorder:

    • The stopRecording method halts recording, and the system ensures logs meet the configured minimum duration before saving them as .gm files inside logsDir.

How to use the Recorder

The following sections will present some use cases for the recorder, demonstrating its functionality in different scenarios.

String tracksPath = await _getTracksPath();
DataSource? dataSource = DataSource.createLiveDataSource();

if (dataSource == null){
showSnackbar("The datasource could not be created");
return;
}

RecorderConfiguration recorderConfiguration = RecorderConfiguration(
dataSource: dataSource,
logsDir: tracksPath,
recordedTypes: [DataType.position],
);

// Create recorder based on configuration
final recorder = Recorder.create(recorderConfiguration);

GemError errorStart = await recorder.startRecording();
if (errorStart != GemError.success) {
showSnackbar("Error starting recording: $errorStart");
}

// Other code

GemError errorStop = await recorder.stopRecording();
if (errorStop != GemError.success) {
showSnackbar("Error stopping recording: $errorStop");
}
warning

The Recorder will only save data explicitly defined in the recordedTypes list, any other data will be ignored.

warning

The startRecording and stopRecording methods must be awaited to ensure proper execution. Otherwise it may lead to unexpected behavior.

tip

Don't forget to request permission for location usage before starting a recorder.

Record audio

The recorder also supports audio recording. To enable this functionality, set the enableAudio parameter in the RecorderConfiguration to true and invoke the startAudioRecording method from the Recorder class. To stop the audio recording, use the stopAudioRecording method. The following code snippet illustrates this process:

RecorderConfiguration recorderConfiguration = RecorderConfiguration(
dataSource: dataSource,
logsDir: tracksPath,
recordedTypes: [DataType.position],
enableAudio: true
);

// Create recorder based on configuration
Recorder recorder = Recorder.create(recorderConfiguration);

GemError errorStart = await recorder.startRecording();
if (errorStart != GemError.success) {
showSnackbar("Error starting recording: $errorStart");
}

// At any moment enable audio recording
recorder.startAudioRecording();

// Other code

// At any moment stop audio recording
recorder.stopAudioRecording();

GemError errorStop = await recorder.stopRecording();
if (errorStop != GemError.success) {
showSnackbar("Error stopping recording: $errorStop");
}
tip

The audio recording will result in a log file of type .mp4. This file also contains the binary data of a .gm file, but it is accessible by system players.

warning

Don't forget to request permission for microphone usage if you set the enableAudio parameter to true.

Record video

The recorder also supports video recording. To enable this functionality, add DataType.camera to the recordedTypes and set the videoQuality parameter in the RecorderConfiguration to your desired resolution, we recommend using Resolution.hd720p. The video recording starts at the call of the startRecording method and stops at the stopRecording method. The following code snippet illustrates this process:

RecorderConfiguration recorderConfiguration = RecorderConfiguration(
dataSource: dataSource,
logsDir: tracksPath,
videoQuality: Resolution.hd720p,
recordedTypes: [DataType.position, DataType.camera],
);

// Create recorder based on configuration
Recorder recorder = Recorder.create(recorderConfiguration);

GemError errorStart = await recorder.startRecording();
if (errorStart != GemError.success) {
showSnackbar("Error starting recording: $errorStart");
}

// Other code

GemError errorStop = await recorder.stopRecording();
if (errorStop != GemError.success) {
showSnackbar("Error stopping recording: $errorStop");
}
tip

The audio recording will result in a log file of type .mp4. This file also contains the binary data of a .gm file, but it is accessible by system players.

warning

Don't forget to request permission for camera usage if you add the DataType.camera parameter to recordedTypes.

Record multimedia

To record a combination of audio, video and sensors you can do that by setting up the RecorderConfiguration with all the desired functionalities. The following code snippet illustrates this process:

RecorderConfiguration recorderConfiguration = RecorderConfiguration(
dataSource: dataSource,
logsDir: tracksPath,
videoQuality: Resolution.hd720p,
recordedTypes: [DataType.position, DataType.camera],
enableAudio: true
);

// Create recorder based on configuration
Recorder recorder = Recorder.create(recorderConfiguration);

GemError errorStart = await recorder.startRecording();
if (errorStart != GemError.success) {
showSnackbar("Error starting recording: $errorStart");
}

// At any moment enable audio recording
recorder.startAudioRecording();

// Other code

// At any moment stop audio recording
recorder.stopAudioRecording();

GemError errorStop = await recorder.stopRecording();
if (errorStop != GemError.success) {
showSnackbar("Error stopping recording: $errorStop");
}
tip

The audio recording will result in a log file of type .mp4. This file also contains the binary data of a .gm file, but it is accessible by system players.

warning

Don't forget to request permission for camera and microphone usage if you set the enableAudio parameter to true and add the DataType.camera parameter to recordedTypes.

Recorder Bookmarks and Metadata

The SDK utilizes the proprietary .gm file format for recordings, offering several advantages over standard file types:

  • Supports multiple data types, including acceleration, rotation, and more.
  • Allows embedding custom user data in binary format.
  • Enables automatic storage management by the SDK to optimize space usage.

Recordings are saved as .gm or .mp4 files by the Recorder. The RecorderBookmarks class provides functionality for managing recordings, including exporting .gm or .mp4 files to other formats such as .gpx, as well as importing external formats and converting them to .gm for seamless SDK integration.

Additionally, the LogMetadata class serves as an object-oriented representation of a .gm or .mp4 file, offering features such as retrieving start and end timestamps, coordinates, and path details at varying levels of precision.

The RecorderBookmarks class for enhanced log management:

  • Export and Import Logs: Convert logs to/from different formats such as GPX, NMEA, and KML.
  • Log Metadata: Retrieve details like start and end timestamps, transport mode and size.

In the following class diagram you can see the main classes used by the RecorderBookmarks and the relationships between them: RecorderBookmarks UML image

Export logs

// Create recorderBookmarks
// It loads all .gm and .mp4 files at logsDir
RecorderBookmarks? bookmarks = RecorderBookmarks.create(tracksPath);

if(bookmarks == null) return;

// Get list of logs
List<String> logList = bookmarks.getLogsList();

// Export last recording as a GPX file with a given name
// Assumes the logList is not empty
GemError exportLogError = bookmarks.exportLog(
logList.last,
FileType.gpx,
exportedFileName: "My_File_Name",
);
if (exportLogError != GemError.success) {
showSnackbar("Error exporting log: $exportLogError");
}

The resulting file will be My_File_Name.gpx. If the name of the exported file is not specified, then the log name will be used.

warning

Exporting a .gm file to other formats may result in data loss, depending on the data types supported by each format.

tip

The exported file will be in the same directory as the original log file.

Import logs

Importing logs involves loading a standard file format (such as gpx, nmea or kml) into a .gm file, enabling it to be processed further.

GemError importError = bookmarks.importLog("path/to/file", importedFileName: "My_File_Name");
if (importError != GemError.success) {
showSnackbar("Error importing log: $importError");
}

Access metadata

Each log contains metadata accessible through the LogMetadata class.

RecorderBookmarks? bookmarks = RecorderBookmarks.create(logsDir);
if(bookmarks != null) {
LogMetadata logMetadata = bookmarks.getLogMetadata(logList.last);
}

The metadata within a LogMetadata object contains:

  • startPosition / endPosition: Geographic coordinates for the log's beginning and end.
  • getUserMetadata / addUserMetadata: Store and retrieve additional data using a key-value approach.
  • preciseRoute: A comprehensive list of all recorded coordinates, capturing the highest level of detail possible.
  • route: A list of route coordinates spaced at least 20 meters apart, with a three-second recording delay between each coordinate.
  • transportMode: RecordingTransportMode of recording.
  • startTimestampInMillis / endTimestampInMillis: Timestamp of the first/last sensor data.
  • durationMillis: Log duration.
  • isProtected: Check if a log file is protected. Protected logs will not be automatically deleted after keepMinSeconds specified in RecorderConfiguration.
  • logSize: Get the log size in bytes.
  • isDataTypeAvailable: Verify if a data type is produced by the log file.
  • soundMarks: A list of recorded soundmarks.
  • activityRecord: The recorded activity details.

To visualize the recorded route, a Path object can be constructed using the route coordinates from the LogMetadata. This path can then be displayed on a map. For more details, refer to the documentation on the path entity and display paths.

Custom user metadata

Users can add custom metadata to a log either during recording or after it has been completed. This is done using the addUserMetadata method, available in both the Recorder and LogMetadata classes. The method requires a String key and the associated data as a Uint8List.

To retrieve previously added metadata, use the getUserMetadata method of the LogMetadata class.

LogMetadata logMetadata = recorderBookmarks!.getLogMetadata(logPath);

// Save image encoded in Uint8List
Uint8List imageSample = ...;
logMetadata.addUserMetadata("ImgData", imageSample);

// Save text by encoding to Uint8List
String text = 'Hello world!';
final encodedText = utf8.encode(text);

// Get image
Uint8List imageData = logMetadata.getUserMetadata("ImgData");

// Get text
Uint8List encodedTextGot = logMetadata.getUserMetadata("textData");
String textData = utf8.decode(encodedTextGot);

Record while app is in background

The recording might fail with error code GemError.general when calling stopRecording if the app is sent to the background during the recording. Set positionActivity to true on the PositionActivity associated to the data source before instantiating the Recorder:

DataSource dataSource = DataSource.createLiveDataSource()!;

PositionSensorConfiguration sensorConfiguration = dataSource.getConfiguration(DataType.position);
sensorConfiguration.allowsBackgroundLocationUpdates = true;
dataSource.setConfiguration(type: DataType.position, config: sensorConfiguration);

RecorderConfiguration recorderConfiguration = RecorderConfiguration(
recordedTypes: [DataType.position],
logsDir: logsDir,
deviceModel: "",
minDurationSeconds: 5,
dataSource: dataSource,
);

Recorder _recorder = Recorder.create(
recorderConfiguration
);

For iOS:

Make sure you add the NSLocationAlwaysUsageDescription and UIBackgroundModes keys into the Info.plist file, within the <dict> block:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Location is needed for map localization and navigation.</string>

and

<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
</array>

For Android:

In the app's manifest file add the ACCESS_BACKGROUND_LOCATION permission.

ActivityRecord

The ActivityRecord class captures details about a recorded activity, including descriptions, sport type, effort level, and visibility settings. It ensures comprehensive metadata for recorded sessions.

Attributes

AttributeDescription
shortDescriptionA brief summary of the activity.
longDescriptionA detailed explanation of the activity.
sportTypeThe type of sport involved in the activity.
effortTypeThe intensity of effort (e.g., easy, moderate, hard).
bikeProfileBike profile details (if applicable).
visibilityDefines who can view the activity.

Setting the activity record

GemError errorStart = await recorder.startRecording();
if (errorStart != GemError.success) {
showSnackbar("Error starting recording: $errorStart");
}

// Other code
recorder.setActivityRecord(ActivityRecord(
shortDescription: "Morning Run",
longDescription: "A 5km run through the park.",
sportType: SportType.run,
effortType: EffortType.moderate,
visibility: ActivityVisibility.everyone,
));

// Other code

GemError errorStop = await recorder.stopRecording();
if (errorStop != GemError.success) {
showSnackbar("Error stopping recording: $errorStop");
}
warning

Call this method while recording, calling it after stopping will not affect the existing recordings.

Getting the activity record

final bookmarks = RecorderBookmarks.create(tracksPath);
if (bookmarks == null){
showSnackbar("Bookmarks could not be created");
return;
}

final logList = bookmarks.getLogsList();

LogMetadata metadata = bookmarks.getLogMetadata(logList.last);
ActivityRecord activityRecord = metadata.activityRecord;