Skip to content

Commit 8a3734d

Browse files
authored
feat(ivs): support recording configuration for channel (#31899)
### Issue # (if applicable) Closes #31780. ### Reason for this change To use recording configuration for IVS channel. ### Description of changes * Add `RecordingConfiguration` Construct. * Add `recordingConfiguration` property to the Channel. ### Description of how you validated changes Add unit tests and integ test. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 74c0ac9 commit 8a3734d

19 files changed

+1751
-0
lines changed

packages/@aws-cdk/aws-ivs-alpha/README.md

+87
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,90 @@ const myChannel = new ivs.Channel(this, 'Channel', {
115115
authorized: true, // default value is false
116116
});
117117
```
118+
119+
## Recording Configurations
120+
121+
An Amazon IVS Recording Configuration stores settings that specify how a channel's live streams should be recorded.
122+
You can configure video quality, thumbnail generation, and where recordings are stored in Amazon S3.
123+
124+
For more information about IVS recording, see [IVS Auto-Record to Amazon S3 | Low-Latency Streaming](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/record-to-s3.html).
125+
126+
You can create a recording configuration:
127+
128+
```ts
129+
// create an S3 bucket for storing recordings
130+
const recordingBucket = new s3.Bucket(this, 'RecordingBucket');
131+
132+
// create a basic recording configuration
133+
const recordingConfiguration = new ivs.RecordingConfiguration(this, 'RecordingConfiguration', {
134+
bucket: recordingBucket,
135+
});
136+
```
137+
138+
### Renditions of a Recording
139+
140+
When you stream content to an Amazon IVS channel, auto-record-to-s3 uses the source video to generate multiple renditions.
141+
142+
For more information, see [Discovering the Renditions of a Recording](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/record-to-s3.html#r2s3-recording-renditions).
143+
144+
```ts
145+
declare const recordingBucket: s3.Bucket;
146+
147+
const recordingConfiguration= new ivs.RecordingConfiguration(this, 'RecordingConfiguration', {
148+
bucket: recordingBucket,
149+
150+
// set rendition configuration
151+
renditionConfiguration: ivs.RenditionConfiguration.custom([ivs.Resolution.HD, ivs.Resolution.SD]),
152+
});
153+
```
154+
155+
### Thumbnail Generation
156+
157+
You can enable or disable the recording of thumbnails for a live session and modify the interval at which thumbnails are generated for the live session.
158+
159+
Thumbnail intervals may range from 1 second to 60 seconds; by default, thumbnail recording is enabled, at an interval of 60 seconds.
160+
161+
For more information, see [Thumbnails](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/record-to-s3.html#r2s3-thumbnails).
162+
163+
```ts
164+
declare const recordingBucket: s3.Bucket;
165+
166+
const recordingConfiguration = new ivs.RecordingConfiguration(this, 'RecordingConfiguration', {
167+
bucket: recordingBucket,
168+
169+
// set thumbnail settings
170+
thumbnailConfiguration: ivs.ThumbnailConfiguration.interval(ivs.Resolution.HD, [ivs.Storage.LATEST, ivs.Storage.SEQUENTIAL], Duration.seconds(30)),
171+
});
172+
```
173+
174+
### Merge Fragmented Streams
175+
176+
The `recordingReconnectWindow` property allows you to specify a window of time (in seconds) during which, if your stream is interrupted and a new stream is started, Amazon IVS tries to record to the same S3 prefix as the previous stream.
177+
178+
In other words, if a broadcast disconnects and then reconnects within the specified interval, the multiple streams are considered a single broadcast and merged together.
179+
180+
For more information, see [Merge Fragmented Streams](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/record-to-s3.html#r2s3-merge-fragmented-streams).
181+
182+
```ts
183+
declare const recordingBucket: s3.Bucket;
184+
185+
const recordingConfiguration= new ivs.RecordingConfiguration(this, 'RecordingConfiguration', {
186+
bucket: recordingBucket,
187+
188+
// set recording reconnect window
189+
recordingReconnectWindow: Duration.seconds(60),
190+
});
191+
```
192+
193+
### Attaching Recording Configuration to a Channel
194+
195+
To enable recording for a channel, specify the recording configuration when creating the channel:
196+
197+
```ts
198+
declare const recordingConfiguration: ivs.RecordingConfiguration;
199+
200+
const channel = new ivs.Channel(this, 'Channel', {
201+
// set recording configuration
202+
recordingConfiguration: recordingConfiguration,
203+
});
204+
```

packages/@aws-cdk/aws-ivs-alpha/lib/channel.ts

+9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Lazy, Names } from 'aws-cdk-lib/core';
33
import { Construct } from 'constructs';
44
import { CfnChannel } from 'aws-cdk-lib/aws-ivs';
55
import { StreamKey } from './stream-key';
6+
import { IRecordingConfiguration } from './recording-configuration';
67

78
/**
89
* Represents an IVS Channel
@@ -153,6 +154,13 @@ export interface ChannelProps {
153154
* @default - Preset.HIGHER_BANDWIDTH_DELIVERY if channelType is ADVANCED_SD or ADVANCED_HD, none otherwise
154155
*/
155156
readonly preset?: Preset;
157+
158+
/**
159+
* A recording configuration for the channel.
160+
*
161+
* @default - recording is disabled
162+
*/
163+
readonly recordingConfiguration?: IRecordingConfiguration;
156164
}
157165

158166
/**
@@ -223,6 +231,7 @@ export class Channel extends ChannelBase {
223231
name: this.physicalName,
224232
type: props.type,
225233
preset,
234+
recordingConfigurationArn: props.recordingConfiguration?.recordingConfigurationArn,
226235
});
227236

228237
this.channelArn = resource.attrArn;
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
export * from './channel';
22
export * from './playback-key-pair';
3+
export * from './recording-configuration';
4+
export * from './rendition-configuration';
35
export * from './stream-key';
6+
export * from './thumbnail-configuration';
7+
export * from './util';
48

59
// AWS::IVS CloudFormation Resources:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import { CfnRecordingConfiguration } from 'aws-cdk-lib/aws-ivs';
2+
import { IBucket } from 'aws-cdk-lib/aws-s3';
3+
import { Duration, Fn, IResource, Resource, Stack, Token } from 'aws-cdk-lib/core';
4+
import { Construct } from 'constructs';
5+
import { RenditionConfiguration } from './rendition-configuration';
6+
import { ThumbnailConfiguration } from './thumbnail-configuration';
7+
8+
/**
9+
* Properties of the IVS Recording configuration
10+
*/
11+
export interface RecordingConfigurationProps {
12+
/**
13+
* S3 bucket where recorded videos will be stored.
14+
*/
15+
readonly bucket: IBucket;
16+
17+
/**
18+
* The name of the Recording configuration.
19+
* The value does not need to be unique.
20+
*
21+
* @default - auto generate
22+
*/
23+
readonly recordingConfigurationName?: string;
24+
25+
/**
26+
* If a broadcast disconnects and then reconnects within the specified interval,
27+
* the multiple streams will be considered a single broadcast and merged together.
28+
*
29+
* `recordingReconnectWindow` must be between 0 and 300 seconds
30+
*
31+
* @default - 0 seconds (means disabled)
32+
*/
33+
readonly recordingReconnectWindow?: Duration;
34+
35+
/**
36+
* A rendition configuration describes which renditions should be recorded for a stream.
37+
*
38+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ivs-recordingconfiguration-renditionconfiguration.html
39+
*
40+
* @default - no rendition configuration
41+
*/
42+
readonly renditionConfiguration?: RenditionConfiguration;
43+
44+
/**
45+
* A thumbnail configuration enables/disables the recording of thumbnails for a live session and controls the interval at which thumbnails are generated for the live session.
46+
*
47+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ivs-recordingconfiguration-thumbnailconfiguration.html
48+
*
49+
* @default - no thumbnail configuration
50+
*/
51+
readonly thumbnailConfiguration?:ThumbnailConfiguration;
52+
}
53+
54+
/**
55+
* Represents the IVS Recording configuration.
56+
*/
57+
export interface IRecordingConfiguration extends IResource {
58+
/**
59+
* The ID of the Recording configuration.
60+
* @attribute
61+
*/
62+
readonly recordingConfigurationId: string;
63+
64+
/**
65+
* The ARN of the Recording configuration.
66+
* @attribute
67+
*/
68+
readonly recordingConfigurationArn: string;
69+
}
70+
71+
/**
72+
* The IVS Recording configuration
73+
*
74+
* @resource AWS::IVS::RecordingConfiguration
75+
*/
76+
export class RecordingConfiguration extends Resource implements IRecordingConfiguration {
77+
/**
78+
* Imports an IVS Recording Configuration from attributes.
79+
*/
80+
public static fromRecordingConfigurationId(scope: Construct, id: string,
81+
recordingConfigurationId: string): IRecordingConfiguration {
82+
83+
class Import extends Resource implements IRecordingConfiguration {
84+
public readonly recordingConfigurationId = recordingConfigurationId;
85+
public readonly recordingConfigurationArn = Stack.of(this).formatArn({
86+
resource: 'recording-configuration',
87+
service: 'ivs',
88+
resourceName: recordingConfigurationId,
89+
});
90+
}
91+
92+
return new Import(scope, id);
93+
}
94+
95+
/**
96+
* Imports an IVS Recording Configuration from its ARN
97+
*/
98+
public static fromArn(scope: Construct, id: string, recordingConfigurationArn: string): IRecordingConfiguration {
99+
const resourceParts = Fn.split('/', recordingConfigurationArn);
100+
101+
if (!resourceParts || resourceParts.length < 2) {
102+
throw new Error(`Unexpected ARN format: ${recordingConfigurationArn}`);
103+
}
104+
105+
const recordingConfigurationId = Fn.select(1, resourceParts);
106+
107+
class Import extends Resource implements IRecordingConfiguration {
108+
public readonly recordingConfigurationId = recordingConfigurationId;
109+
public readonly recordingConfigurationArn = recordingConfigurationArn;
110+
}
111+
112+
return new Import(scope, id);
113+
}
114+
115+
/**
116+
* The ID of the Recording configuration.
117+
* @attribute
118+
*/
119+
readonly recordingConfigurationId: string;
120+
121+
/**
122+
* The ARN of the Recording configuration.
123+
* @attribute
124+
*/
125+
readonly recordingConfigurationArn: string;
126+
127+
private readonly props: RecordingConfigurationProps;
128+
129+
public constructor(scope: Construct, id: string, props: RecordingConfigurationProps) {
130+
super(scope, id, {
131+
physicalName: props.recordingConfigurationName,
132+
});
133+
134+
this.props = props;
135+
136+
this.validateRecordingConfigurationName();
137+
this.validateRecordingReconnectWindowSeconds();
138+
139+
const resource = new CfnRecordingConfiguration(this, 'Resource', {
140+
destinationConfiguration: {
141+
s3: {
142+
bucketName: this.props.bucket.bucketName,
143+
},
144+
},
145+
name: this.props.recordingConfigurationName,
146+
recordingReconnectWindowSeconds: this.props.recordingReconnectWindow?.toSeconds(),
147+
renditionConfiguration: this._renderRenditionConfiguration(),
148+
thumbnailConfiguration: this._renderThumbnailConfiguration(),
149+
});
150+
151+
this.recordingConfigurationId = resource.ref;
152+
this.recordingConfigurationArn = resource.attrArn;
153+
}
154+
155+
private _renderRenditionConfiguration(): CfnRecordingConfiguration.RenditionConfigurationProperty | undefined {
156+
if (!this.props.renditionConfiguration) {
157+
return;
158+
}
159+
160+
return {
161+
renditions: this.props.renditionConfiguration.renditions,
162+
renditionSelection: this.props.renditionConfiguration.renditionSelection,
163+
};
164+
};
165+
166+
private _renderThumbnailConfiguration(): CfnRecordingConfiguration.ThumbnailConfigurationProperty | undefined {
167+
if (!this.props.thumbnailConfiguration) {
168+
return;
169+
}
170+
171+
return {
172+
recordingMode: this.props.thumbnailConfiguration.recordingMode,
173+
resolution: this.props.thumbnailConfiguration.resolution,
174+
storage: this.props.thumbnailConfiguration.storage,
175+
targetIntervalSeconds: this.props.thumbnailConfiguration.targetInterval?.toSeconds(),
176+
};
177+
};
178+
179+
private validateRecordingConfigurationName(): undefined {
180+
const recordingConfigurationName = this.props.recordingConfigurationName;
181+
182+
if (recordingConfigurationName == undefined || Token.isUnresolved(recordingConfigurationName)) {
183+
return;
184+
}
185+
186+
if (!/^[a-zA-Z0-9-_]*$/.test(recordingConfigurationName)) {
187+
throw new Error(`\`recordingConfigurationName\` must consist only of alphanumeric characters, hyphens or underbars, got: ${recordingConfigurationName}.`);
188+
}
189+
190+
if (recordingConfigurationName.length > 128) {
191+
throw new Error(`\`recordingConfigurationName\` must be less than or equal to 128 characters, got: ${recordingConfigurationName.length} characters.`);
192+
}
193+
};
194+
195+
private validateRecordingReconnectWindowSeconds(): undefined {
196+
const recordingReconnectWindow = this.props.recordingReconnectWindow;
197+
198+
if (recordingReconnectWindow === undefined || Token.isUnresolved(recordingReconnectWindow)) {
199+
return;
200+
}
201+
202+
if (0 < recordingReconnectWindow.toMilliseconds() && recordingReconnectWindow.toMilliseconds() < Duration.seconds(1).toMilliseconds()) {
203+
throw new Error(`\`recordingReconnectWindow\` must be between 0 and 300 seconds, got ${recordingReconnectWindow.toMilliseconds()} milliseconds.`);
204+
}
205+
206+
if (recordingReconnectWindow.toSeconds() > 300) {
207+
throw new Error(`\`recordingReconnectWindow\` must be between 0 and 300 seconds, got ${recordingReconnectWindow.toSeconds()} seconds.`);
208+
}
209+
};
210+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Resolution } from './util';
2+
3+
/**
4+
* Rendition selection mode.
5+
*/
6+
export enum RenditionSelection {
7+
/**
8+
* Record all available renditions.
9+
*/
10+
ALL = 'ALL',
11+
12+
/**
13+
* Does not record any video. This option is useful if you just want to record thumbnails.
14+
*/
15+
NONE = 'NONE',
16+
17+
/**
18+
* Select a subset of video renditions to record.
19+
*/
20+
CUSTOM = 'CUSTOM',
21+
}
22+
23+
/**
24+
* Rendition configuration for IVS Recording configuration
25+
*/
26+
export class RenditionConfiguration {
27+
/**
28+
* Record all available renditions.
29+
*/
30+
public static all(): RenditionConfiguration {
31+
return new RenditionConfiguration(RenditionSelection.ALL);
32+
}
33+
34+
/**
35+
* Does not record any video.
36+
*/
37+
public static none(): RenditionConfiguration {
38+
return new RenditionConfiguration(RenditionSelection.NONE);
39+
}
40+
41+
/**
42+
* Record a subset of video renditions.
43+
*
44+
* @param renditions A list of which renditions are recorded for a stream.
45+
*/
46+
public static custom(renditions: Resolution[]): RenditionConfiguration {
47+
return new RenditionConfiguration(RenditionSelection.CUSTOM, renditions);
48+
}
49+
50+
/**
51+
* @param renditionSelection The set of renditions are recorded for a stream.
52+
* @param renditions A list of which renditions are recorded for a stream. If you do not specify this property, no resolution is selected.
53+
*/
54+
private constructor(public readonly renditionSelection: RenditionSelection, public readonly renditions?: Resolution[]) { }
55+
}

0 commit comments

Comments
 (0)