-
Notifications
You must be signed in to change notification settings - Fork 0
Recording
Since v0.6.0. steam.recording is the curated layer over ISteamTimeline:
mark what happened while Steam Game Recording was running, so the player can
find the interesting moments again and clip them. It is called recording
rather than timeline because the generated ISteamTimeline class already
owns steam.timeline.
Two things get marked. An event is a moment, or a range of moments, worth clipping: a boss kill, a death, a lap time. A phase is a longer stretch of play, for example one match or one run, that carries tags and attributes and that the user can jump to in the overlay.
Every marking call is fire and forget. Steam has no result for them, and a user with Game Recording turned off silently records nothing, so nothing here throws for that. Event handles still come back from addEvent and startEvent; they just refer to a recording that does not exist, which is what eventRecordingExists is for.
The Timeline instance is created lazily and cached on the Steam object.
setGameMode(mode: number): voidTells Steam what the player is doing right now. Steam colours the timeline bar
by mode, so a menu or a loading screen is visibly not gameplay. mode is an
ETimelineGameMode: 1 playing, 2 staging, 3 menus, 4 loading screen.
import { init, flat } from 'steamwand.js';
const steam = init({ appId: 480 });
steam.recording.setGameMode(flat.ETimelineGameMode.k_ETimelineGameMode_Playing);
steam.close();setTooltip(text: string, timeDelta?: number): void
clearTooltip(timeDelta?: number): voidThe text shown when the user hovers the timeline at this moment. Steam keeps it
until it is changed or cleared, so this is state, not an event: set it when the
situation changes, not every frame. timeDelta (default 0) is seconds
relative to now, and negative points into the past.
steam.recording.setTooltip('Level 3, 2 lives left');addEvent(event: TimelineEvent): bigintMarks one event and returns its handle. Instantaneous without duration, a
range with it. Use this when the event is over by the time you know about it;
for something still running, use
startEvent.
The handle is 0n when Steam did not record an event, for example because the
user has Game Recording off. That is not an error and this does not throw.
import { init } from 'steamwand.js';
const steam = init({ appId: 480 });
const id = steam.recording.addEvent({
title: 'Boss defeated',
description: 'Beat the first boss without dying',
icon: 'steam_achievement',
});
steam.close();startEvent(event: Omit<TimelineEvent, 'duration'>): bigint
updateEvent(id: bigint, event: Omit<TimelineEvent, 'startOffset' | 'duration'>): void
endEvent(id: bigint, endOffset?: number): void
removeEvent(id: bigint): voidstartEvent opens an event whose end is not known yet. It stays open until
endEvent, and Steam ends any still-open event when the game exits.
updateEvent replaces the text, icon and priorities while it runs, for example
to count the kills in a fight that is still going: every field is sent, so pass
the whole event, not only what changed. endOffset (default 0) is seconds
relative to now. removeEvent deletes an event that turned out not to matter,
whether it came from here or from addEvent.
const fight = steam.recording.startEvent({
title: 'Boss fight',
description: 'Fighting the first boss',
icon: 'steam_combat',
});
// later:
steam.recording.endEvent(fight);eventRecordingExists(id: bigint): Promise<boolean>Asks whether Steam actually recorded video around one event. False for every event when the user has Game Recording off, and false for an event outside the recording buffer. This is how a game decides whether to offer the user a clip.
if (await steam.recording.eventRecordingExists(id)) steam.recording.openOverlayToEvent(id);startPhase(): void
endPhase(): voidOpen and close a game phase. Tags and attributes set while a phase is open belong to it. Starting a phase ends the one before it.
steam.recording.startPhase();
steam.recording.setPhaseId('match-4711');
steam.recording.addPhaseTag('Dust II', 'steam_map', 'Map');
steam.recording.setPhaseAttribute('Score', '16-14');
// ... play ...
steam.recording.endPhase();setPhaseId(id: string): void
addPhaseTag(name: string, icon: string, group: string, priority?: number): void
setPhaseAttribute(group: string, value: string, priority?: number): voidsetPhaseId names the open phase, at most 63 UTF-8 bytes plus the terminator.
It is what phaseRecordingExists and
openOverlayToPhase take, so use
something your game can recognise later. Two phases may share an id, which
groups them.
A tag is a fact about the phase that repeats: the map, the character, the game
mode. Steam groups tags by group in the UI. An attribute is a single value
per group, not a list, so setting the same group again replaces it; use it for
things like the final score. priority (default 0) ranks entries inside a
group, higher first.
phaseRecordingExists(phaseId: string): Promise<PhaseRecording>Asks what Steam recorded during a phase. All counters are zero when the user has Game Recording off, or when no phase ever carried that id, so this never fails for an unknown phase: it answers with an empty recording.
const r = await steam.recording.phaseRecordingExists('match-4711');
if (r.recordingMs > 0n) steam.recording.openOverlayToPhase('match-4711');openOverlayToPhase(id: string): void
openOverlayToEvent(id: bigint): voidOpen the Steam overlay on a phase or on one event, where the user can save a clip. The overlay draws into the game's own renderer, so a plain Node or Electron process gets nothing from these; same caveat as the whole Overlay layer.
| Field | Type | Meaning |
|---|---|---|
title |
string |
Short label shown on the timeline. |
description |
string |
Longer text shown on hover. |
icon |
string |
Steam icon name, for example steam_achievement. |
priority |
number |
Ranks this event against others at the same moment. Default 0. |
startOffset |
number |
Seconds relative to now; negative points into the past. Default 0. |
duration |
number |
Length in seconds. Omit for an instant. |
clipPriority |
number |
ETimelineEventClipPriority: 1 none, 2 standard (the default), 3 featured. |
Valve lists the built-in icon names, and lets you upload your own, at https://partner.steamgames.com/doc/features/timeline
| Field | Type | Meaning |
|---|---|---|
phaseId |
string |
The phase id that was asked about. |
recordingMs |
bigint |
Total recorded milliseconds, 0n if nothing was recorded. |
longestClipMs |
bigint |
Length of the longest clip in milliseconds. |
clipCount |
number |
Clips the user saved from the phase. |
screenshotCount |
number |
Screenshots taken during the phase. |
No SteamResultError: nothing in ISteamTimeline returns an EResult.
eventRecordingExists and
phaseRecordingExists are the only async methods, and
they reject with SteamApiCallError when the call could not be completed at
all. Every other method returns void or a handle and throws nothing, so a
call that did nothing looks exactly like a call that worked.
- Record anything itself. Steam Game Recording is a client feature the user turns on. This layer only annotates what the client records.
- Read clips back. Steam exposes counters, not files. There is no way to get at the video from here.
-
Leave anything on
ISteamTimelineunwrapped. The whole interface is covered; the raw calls stay onsteam.timelineif you want them.
Flat API explains the calling convention.
Next: Capture for screenshots, which is the other way a player keeps a moment.