diff --git a/js/app/store/slices/contentMetadataSlice.ts b/js/app/store/slices/contentMetadataSlice.ts
index 91fbbe1f..f0dbf54e 100644
--- a/js/app/store/slices/contentMetadataSlice.ts
+++ b/js/app/store/slices/contentMetadataSlice.ts
@@ -14,7 +14,7 @@ export interface ContentMetadataSlice {
// actions
createContentMetadata: (params: {
contentWarnings?: string[];
- distributionPolicy?: { deleteAfter?: number };
+ distributionPolicy?: { deleteAfter?: number; allowAiTraining?: boolean };
contentRights?: {
creator?: string;
copyrightNotice?: string;
@@ -27,7 +27,7 @@ export interface ContentMetadataSlice {
rkey?: string;
livestreamRef?: { uri: string; cid: string };
contentWarnings?: string[];
- distributionPolicy?: { deleteAfter?: number };
+ distributionPolicy?: { deleteAfter?: number; allowAiTraining?: boolean };
contentRights?: {
creator?: string;
copyrightNotice?: string;
@@ -80,7 +80,10 @@ export const createContentMetadataSlice: StateCreator<
...(contentWarnings.length > 0 && {
contentWarnings: { warnings: contentWarnings },
}),
- ...(distributionPolicy.deleteAfter && { distributionPolicy }),
+ ...((distributionPolicy.deleteAfter ||
+ distributionPolicy.allowAiTraining !== undefined) && {
+ distributionPolicy,
+ }),
...(contentRights &&
Object.keys(contentRights).length > 0 && {
contentRights,
@@ -142,7 +145,10 @@ export const createContentMetadataSlice: StateCreator<
...(contentWarnings.length > 0 && {
contentWarnings: { warnings: contentWarnings },
}),
- ...(distributionPolicy.deleteAfter && { distributionPolicy }),
+ ...((distributionPolicy.deleteAfter ||
+ distributionPolicy.allowAiTraining !== undefined) && {
+ distributionPolicy,
+ }),
...(contentRights &&
Object.keys(contentRights).length > 0 && {
contentRights,
diff --git a/js/components/src/components/content-metadata/content-metadata-form.tsx b/js/components/src/components/content-metadata/content-metadata-form.tsx
index a21ec815..f4d19a24 100644
--- a/js/components/src/components/content-metadata/content-metadata-form.tsx
+++ b/js/components/src/components/content-metadata/content-metadata-form.tsx
@@ -198,14 +198,19 @@ export const ContentMetadataForm = forwardRef(
({
deleteAfter,
allowedBroadcasters,
+ allowAiTraining,
}: {
deleteAfter?: string;
allowedBroadcasters?: string;
+ allowAiTraining?: boolean;
}) => {
let newDistributionPolicy: place.stream.metadata.distributionPolicy.Main =
{
...distributionPolicy,
};
+ if (typeof allowAiTraining === "boolean") {
+ newDistributionPolicy.allowAiTraining = allowAiTraining;
+ }
if (typeof deleteAfter === "string") {
let duration = parseInt(deleteAfter, 10);
if (isNaN(duration)) {
@@ -304,7 +309,10 @@ export const ContentMetadataForm = forwardRef(
metadata.contentRights = filteredRights;
}
+ // AI training is opt-in: absent an explicit choice, save the record
+ // with training disallowed.
metadata.distributionPolicy = {
+ allowAiTraining: false,
...distributionPolicy,
};
@@ -336,6 +344,7 @@ export const ContentMetadataForm = forwardRef(
}, [
contentWarnings,
contentRights,
+ distributionPolicy,
selectedLicense,
customLicenseText,
hasMetadata,
@@ -760,6 +769,24 @@ export const ContentMetadataForm = forwardRef(
)}
+
+
+ handleDistributionPolicyChange({
+ allowAiTraining: checked,
+ })
+ }
+ label={
+ "Allow your content to be used for generative AI training"
+ }
+ style={[{ fontSize: 12 }]}
+ />
+
+
Date: Wed, 12 Aug 2026 20:01:05 -0700
Subject: [PATCH 2/9] metadata: rename allowAiTraining -> allowGenAiTraining
Be explicit that the flag governs generative AI only: Streamplace may
still run non-generative machine learning (moderation, accessibility,
speech models), and the lexicon description now says so. The CAWG
assertion was already scoped to cawg.ai_generative_training.
Co-Authored-By: Claude Fable 5
---
js/app/store/slices/contentMetadataSlice.ts | 8 ++---
.../content-metadata-form.tsx | 14 ++++-----
...lace-stream-metadata-distributionpolicy.md | 14 ++++-----
.../stream/metadata/distributionPolicy.json | 4 +--
pkg/localdb/segment.go | 8 ++---
pkg/media/distribution_policy_test.go | 30 +++++++++----------
pkg/media/manifest_builder.go | 10 +++----
pkg/media/manifest_builder_test.go | 20 ++++++-------
pkg/media/media.go | 4 +--
pkg/placestream/metadatadistributionpolicy.go | 4 +--
10 files changed, 58 insertions(+), 58 deletions(-)
diff --git a/js/app/store/slices/contentMetadataSlice.ts b/js/app/store/slices/contentMetadataSlice.ts
index f0dbf54e..297ca759 100644
--- a/js/app/store/slices/contentMetadataSlice.ts
+++ b/js/app/store/slices/contentMetadataSlice.ts
@@ -14,7 +14,7 @@ export interface ContentMetadataSlice {
// actions
createContentMetadata: (params: {
contentWarnings?: string[];
- distributionPolicy?: { deleteAfter?: number; allowAiTraining?: boolean };
+ distributionPolicy?: { deleteAfter?: number; allowGenAiTraining?: boolean };
contentRights?: {
creator?: string;
copyrightNotice?: string;
@@ -27,7 +27,7 @@ export interface ContentMetadataSlice {
rkey?: string;
livestreamRef?: { uri: string; cid: string };
contentWarnings?: string[];
- distributionPolicy?: { deleteAfter?: number; allowAiTraining?: boolean };
+ distributionPolicy?: { deleteAfter?: number; allowGenAiTraining?: boolean };
contentRights?: {
creator?: string;
copyrightNotice?: string;
@@ -81,7 +81,7 @@ export const createContentMetadataSlice: StateCreator<
contentWarnings: { warnings: contentWarnings },
}),
...((distributionPolicy.deleteAfter ||
- distributionPolicy.allowAiTraining !== undefined) && {
+ distributionPolicy.allowGenAiTraining !== undefined) && {
distributionPolicy,
}),
...(contentRights &&
@@ -146,7 +146,7 @@ export const createContentMetadataSlice: StateCreator<
contentWarnings: { warnings: contentWarnings },
}),
...((distributionPolicy.deleteAfter ||
- distributionPolicy.allowAiTraining !== undefined) && {
+ distributionPolicy.allowGenAiTraining !== undefined) && {
distributionPolicy,
}),
...(contentRights &&
diff --git a/js/components/src/components/content-metadata/content-metadata-form.tsx b/js/components/src/components/content-metadata/content-metadata-form.tsx
index f4d19a24..26a3e3dc 100644
--- a/js/components/src/components/content-metadata/content-metadata-form.tsx
+++ b/js/components/src/components/content-metadata/content-metadata-form.tsx
@@ -198,18 +198,18 @@ export const ContentMetadataForm = forwardRef(
({
deleteAfter,
allowedBroadcasters,
- allowAiTraining,
+ allowGenAiTraining,
}: {
deleteAfter?: string;
allowedBroadcasters?: string;
- allowAiTraining?: boolean;
+ allowGenAiTraining?: boolean;
}) => {
let newDistributionPolicy: place.stream.metadata.distributionPolicy.Main =
{
...distributionPolicy,
};
- if (typeof allowAiTraining === "boolean") {
- newDistributionPolicy.allowAiTraining = allowAiTraining;
+ if (typeof allowGenAiTraining === "boolean") {
+ newDistributionPolicy.allowGenAiTraining = allowGenAiTraining;
}
if (typeof deleteAfter === "string") {
let duration = parseInt(deleteAfter, 10);
@@ -312,7 +312,7 @@ export const ContentMetadataForm = forwardRef(
// AI training is opt-in: absent an explicit choice, save the record
// with training disallowed.
metadata.distributionPolicy = {
- allowAiTraining: false,
+ allowGenAiTraining: false,
...distributionPolicy,
};
@@ -774,10 +774,10 @@ export const ContentMetadataForm = forwardRef(
position="top"
>
handleDistributionPolicyChange({
- allowAiTraining: checked,
+ allowGenAiTraining: checked,
})
}
label={
diff --git a/js/docs/src/content/docs/lex-reference/metadata/place-stream-metadata-distributionpolicy.md b/js/docs/src/content/docs/lex-reference/metadata/place-stream-metadata-distributionpolicy.md
index 908471d6..ed55ad6c 100644
--- a/js/docs/src/content/docs/lex-reference/metadata/place-stream-metadata-distributionpolicy.md
+++ b/js/docs/src/content/docs/lex-reference/metadata/place-stream-metadata-distributionpolicy.md
@@ -17,11 +17,11 @@ Distribution and rebroadcast policy.
**Properties:**
-| Name | Type | Req'd | Description | Constraints |
-| --------------------- | ----------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
-| `deleteAfter` | `integer` | ❌ | Duration in seconds after which segments should be deleted. Each segment will expire N seconds after its creation time. -1 to allow indefinite archival. | |
-| `allowedBroadcasters` | Array of `string` | ❌ | List of did:webs of the broadcasters you want to allow to distribute your content. "\*" allows anyone. Starting a line with a "!" bans that broadcaster. | |
-| `allowAiTraining` | `boolean` | ❌ | Whether this content may be used as training data for generative AI models. Absent means undeclared; Streamplace treats undeclared as false and stamps an explicit value into every minted segment. | |
+| Name | Type | Req'd | Description | Constraints |
+| --------------------- | ----------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
+| `deleteAfter` | `integer` | ❌ | Duration in seconds after which segments should be deleted. Each segment will expire N seconds after its creation time. -1 to allow indefinite archival. | |
+| `allowedBroadcasters` | Array of `string` | ❌ | List of did:webs of the broadcasters you want to allow to distribute your content. "\*" allows anyone. Starting a line with a "!" bans that broadcaster. | |
+| `allowGenAiTraining` | `boolean` | ❌ | Whether this content may be used as training data for generative AI models. Absent means undeclared; Streamplace treats undeclared as false and stamps an explicit value into every minted segment. Covers generative AI only — non-generative machine learning (e.g. moderation, accessibility, speech models) is not governed by this flag. | |
---
@@ -47,9 +47,9 @@ Distribution and rebroadcast policy.
"type": "string"
}
},
- "allowAiTraining": {
+ "allowGenAiTraining": {
"type": "boolean",
- "description": "Whether this content may be used as training data for generative AI models. Absent means undeclared; Streamplace treats undeclared as false and stamps an explicit value into every minted segment."
+ "description": "Whether this content may be used as training data for generative AI models. Absent means undeclared; Streamplace treats undeclared as false and stamps an explicit value into every minted segment. Covers generative AI only — non-generative machine learning (e.g. moderation, accessibility, speech models) is not governed by this flag."
}
}
}
diff --git a/lexicons/place/stream/metadata/distributionPolicy.json b/lexicons/place/stream/metadata/distributionPolicy.json
index eec3c1e4..9965e671 100644
--- a/lexicons/place/stream/metadata/distributionPolicy.json
+++ b/lexicons/place/stream/metadata/distributionPolicy.json
@@ -17,9 +17,9 @@
"type": "string"
}
},
- "allowAiTraining": {
+ "allowGenAiTraining": {
"type": "boolean",
- "description": "Whether this content may be used as training data for generative AI models. Absent means undeclared; Streamplace treats undeclared as false and stamps an explicit value into every minted segment."
+ "description": "Whether this content may be used as training data for generative AI models. Absent means undeclared; Streamplace treats undeclared as false and stamps an explicit value into every minted segment. Covers generative AI only — non-generative machine learning (e.g. moderation, accessibility, speech models) is not governed by this flag."
}
}
}
diff --git a/pkg/localdb/segment.go b/pkg/localdb/segment.go
index 2e137ae5..30c0aad9 100644
--- a/pkg/localdb/segment.go
+++ b/pkg/localdb/segment.go
@@ -86,7 +86,7 @@ func (c ContentRights) Value() (driver.Value, error) {
// DistributionPolicy represents distribution policy information
type DistributionPolicy struct {
DeleteAfterSeconds *int64 `json:"deleteAfterSeconds,omitempty"`
- AllowAiTraining *bool `json:"allowAiTraining,omitempty"`
+ AllowGenAiTraining *bool `json:"allowGenAiTraining,omitempty"`
}
// Scan scan value into DistributionPolicy, implements sql.Scanner interface
@@ -185,10 +185,10 @@ func (s *Segment) ToStreamplaceSegment() (*placestream.Segment, error) {
}
var distributionPolicy *placestream.MetadataDistributionPolicy
- if s.DistributionPolicy != nil && (s.DistributionPolicy.DeleteAfterSeconds != nil || s.DistributionPolicy.AllowAiTraining != nil) {
+ if s.DistributionPolicy != nil && (s.DistributionPolicy.DeleteAfterSeconds != nil || s.DistributionPolicy.AllowGenAiTraining != nil) {
distributionPolicy = &placestream.MetadataDistributionPolicy{
- DeleteAfter: s.DistributionPolicy.DeleteAfterSeconds,
- AllowAiTraining: s.DistributionPolicy.AllowAiTraining,
+ DeleteAfter: s.DistributionPolicy.DeleteAfterSeconds,
+ AllowGenAiTraining: s.DistributionPolicy.AllowGenAiTraining,
}
}
diff --git a/pkg/media/distribution_policy_test.go b/pkg/media/distribution_policy_test.go
index f1efefc5..3d195c44 100644
--- a/pkg/media/distribution_policy_test.go
+++ b/pkg/media/distribution_policy_test.go
@@ -23,38 +23,38 @@ func manifestWithDistributionPolicy(policy map[string]any) *c2patypes.Manifest {
}
}
-// extractDistributionPolicy carries allowAiTraining out of the manifest, alone
+// extractDistributionPolicy carries allowGenAiTraining out of the manifest, alone
// or alongside deleteAfter, and stays nil when neither is declared.
-func TestExtractDistributionPolicyAllowAiTraining(t *testing.T) {
+func TestExtractDistributionPolicyAllowGenAiTraining(t *testing.T) {
now := aqtime.FromTime(time.Now())
policy := extractDistributionPolicy(manifestWithDistributionPolicy(map[string]any{
- "allowAiTraining": false,
+ "allowGenAiTraining": false,
}), now)
require.NotNil(t, policy)
- require.NotNil(t, policy.AllowAiTraining)
- require.False(t, *policy.AllowAiTraining)
+ require.NotNil(t, policy.AllowGenAiTraining)
+ require.False(t, *policy.AllowGenAiTraining)
require.Nil(t, policy.DeleteAfterSeconds)
policy = extractDistributionPolicy(manifestWithDistributionPolicy(map[string]any{
- "allowAiTraining": true,
- "deleteAfter": 300,
+ "allowGenAiTraining": true,
+ "deleteAfter": 300,
}), now)
require.NotNil(t, policy)
- require.NotNil(t, policy.AllowAiTraining)
- require.True(t, *policy.AllowAiTraining)
+ require.NotNil(t, policy.AllowGenAiTraining)
+ require.True(t, *policy.AllowGenAiTraining)
require.NotNil(t, policy.DeleteAfterSeconds)
require.Equal(t, int64(300), *policy.DeleteAfterSeconds)
policy = extractDistributionPolicy(manifestWithDistributionPolicy(map[string]any{
"allowedBroadcasters": []string{"*"},
}), now)
- require.Nil(t, policy, "a policy with neither deleteAfter nor allowAiTraining stays nil")
+ require.Nil(t, policy, "a policy with neither deleteAfter nor allowGenAiTraining stays nil")
}
-// The minted place.stream.segment record carries allowAiTraining even when
+// The minted place.stream.segment record carries allowGenAiTraining even when
// deleteAfter is unset.
-func TestSegmentRecordCarriesAllowAiTraining(t *testing.T) {
+func TestSegmentRecordCarriesAllowGenAiTraining(t *testing.T) {
notAllowed := false
seg := &localdb.Segment{
ID: "test-segment",
@@ -65,13 +65,13 @@ func TestSegmentRecordCarriesAllowAiTraining(t *testing.T) {
Audio: []*localdb.SegmentMediadataAudio{{Rate: 48000, Channels: 2}},
},
DistributionPolicy: &localdb.DistributionPolicy{
- AllowAiTraining: ¬Allowed,
+ AllowGenAiTraining: ¬Allowed,
},
}
record, err := seg.ToStreamplaceSegment()
require.NoError(t, err)
require.NotNil(t, record.DistributionPolicy)
- require.NotNil(t, record.DistributionPolicy.AllowAiTraining)
- require.False(t, *record.DistributionPolicy.AllowAiTraining)
+ require.NotNil(t, record.DistributionPolicy.AllowGenAiTraining)
+ require.False(t, *record.DistributionPolicy.AllowGenAiTraining)
require.Nil(t, record.DistributionPolicy.DeleteAfter)
}
diff --git a/pkg/media/manifest_builder.go b/pkg/media/manifest_builder.go
index 26662659..1463d9b7 100644
--- a/pkg/media/manifest_builder.go
+++ b/pkg/media/manifest_builder.go
@@ -159,9 +159,9 @@ func (mb *ManifestBuilder) BuildManifest(ctx context.Context, streamerName strin
if streamplaceMetadata.DistributionPolicy == nil {
streamplaceMetadata.DistributionPolicy = &placestream.MetadataDistributionPolicy{}
}
- if streamplaceMetadata.DistributionPolicy.AllowAiTraining == nil {
+ if streamplaceMetadata.DistributionPolicy.AllowGenAiTraining == nil {
notAllowed := false
- streamplaceMetadata.DistributionPolicy.AllowAiTraining = ¬Allowed
+ streamplaceMetadata.DistributionPolicy.AllowGenAiTraining = ¬Allowed
}
metadataObj, err := toObj(streamplaceMetadata)
if err != nil {
@@ -172,7 +172,7 @@ func (mb *ManifestBuilder) BuildManifest(ctx context.Context, streamerName strin
"label": "place.stream.metadata.configuration",
"data": metadataObj,
},
- trainingMiningAssertion(*streamplaceMetadata.DistributionPolicy.AllowAiTraining),
+ trainingMiningAssertion(*streamplaceMetadata.DistributionPolicy.AllowGenAiTraining),
)
// Update the manifest title with the retrieved livestream title
@@ -199,9 +199,9 @@ func (mb *ManifestBuilder) BuildManifest(ctx context.Context, streamerName strin
// trainingMiningAssertion builds the CAWG training and data mining assertion
// (https://cawg.io/training-and-data-mining/1.1/), the standard machine-readable
// way to declare whether content may be used to train generative AI models.
-func trainingMiningAssertion(allowAiTraining bool) obj {
+func trainingMiningAssertion(allowGenAiTraining bool) obj {
use := "notAllowed"
- if allowAiTraining {
+ if allowGenAiTraining {
use = "allowed"
}
return obj{
diff --git a/pkg/media/manifest_builder_test.go b/pkg/media/manifest_builder_test.go
index b423eb14..57d8f803 100644
--- a/pkg/media/manifest_builder_test.go
+++ b/pkg/media/manifest_builder_test.go
@@ -57,7 +57,7 @@ func buildManifestAssertions(t *testing.T, metadata *model.MetadataConfiguration
return out
}
-func aiTrainingUse(t *testing.T, assertions map[string]map[string]any) string {
+func genAiTrainingUse(t *testing.T, assertions map[string]map[string]any) string {
data, ok := assertions["cawg.training-mining"]
require.True(t, ok, "manifest must carry the cawg.training-mining assertion")
entries, ok := data["entries"].(map[string]any)
@@ -69,20 +69,20 @@ func aiTrainingUse(t *testing.T, assertions map[string]map[string]any) string {
return use
}
-func allowAiTrainingField(t *testing.T, assertions map[string]map[string]any) any {
+func allowGenAiTrainingField(t *testing.T, assertions map[string]map[string]any) any {
data, ok := assertions["place.stream.metadata.configuration"]
require.True(t, ok, "manifest must carry the metadata configuration assertion")
policy, ok := data["distributionPolicy"].(map[string]any)
require.True(t, ok, "metadata configuration must carry a distributionPolicy")
- return policy["allowAiTraining"]
+ return policy["allowGenAiTraining"]
}
// A streamer with no metadata configuration at all still gets an explicit
// "AI training not allowed" stamped into every minted segment.
func TestBuildManifestAiTrainingDefaultDeny(t *testing.T) {
assertions := buildManifestAssertions(t, nil)
- require.Equal(t, "notAllowed", aiTrainingUse(t, assertions))
- require.Equal(t, false, allowAiTrainingField(t, assertions))
+ require.Equal(t, "notAllowed", genAiTrainingUse(t, assertions))
+ require.Equal(t, false, allowGenAiTrainingField(t, assertions))
}
// A metadata configuration that never mentions AI training also defaults to
@@ -95,8 +95,8 @@ func TestBuildManifestAiTrainingUndeclaredDeny(t *testing.T) {
},
})
assertions := buildManifestAssertions(t, row)
- require.Equal(t, "notAllowed", aiTrainingUse(t, assertions))
- require.Equal(t, false, allowAiTrainingField(t, assertions))
+ require.Equal(t, "notAllowed", genAiTrainingUse(t, assertions))
+ require.Equal(t, false, allowGenAiTrainingField(t, assertions))
// the rest of the distribution policy survives the stamping
policy := assertions["place.stream.metadata.configuration"]["distributionPolicy"].(map[string]any)
@@ -108,10 +108,10 @@ func TestBuildManifestAiTrainingExplicitAllow(t *testing.T) {
allow := true
row := metadataConfigurationRow(t, &placestream.MetadataConfiguration{
DistributionPolicy: &placestream.MetadataDistributionPolicy{
- AllowAiTraining: &allow,
+ AllowGenAiTraining: &allow,
},
})
assertions := buildManifestAssertions(t, row)
- require.Equal(t, "allowed", aiTrainingUse(t, assertions))
- require.Equal(t, true, allowAiTrainingField(t, assertions))
+ require.Equal(t, "allowed", genAiTrainingUse(t, assertions))
+ require.Equal(t, true, allowGenAiTrainingField(t, assertions))
}
diff --git a/pkg/media/media.go b/pkg/media/media.go
index 8893f0b1..81096c8a 100644
--- a/pkg/media/media.go
+++ b/pkg/media/media.go
@@ -428,7 +428,7 @@ func extractDistributionPolicy(mani *c2patypes.Manifest, segmentStart aqtime.AQT
}
policy := &localdb.DistributionPolicy{
- AllowAiTraining: metadataConfig.DistributionPolicy.AllowAiTraining,
+ AllowGenAiTraining: metadataConfig.DistributionPolicy.AllowGenAiTraining,
}
if metadataConfig.DistributionPolicy.DeleteAfter != nil {
@@ -437,7 +437,7 @@ func extractDistributionPolicy(mani *c2patypes.Manifest, segmentStart aqtime.AQT
policy.DeleteAfterSeconds = &deleteAfterSeconds
}
- if policy.DeleteAfterSeconds == nil && policy.AllowAiTraining == nil {
+ if policy.DeleteAfterSeconds == nil && policy.AllowGenAiTraining == nil {
return nil
}
diff --git a/pkg/placestream/metadatadistributionpolicy.go b/pkg/placestream/metadatadistributionpolicy.go
index 4598ab67..6449793f 100644
--- a/pkg/placestream/metadatadistributionpolicy.go
+++ b/pkg/placestream/metadatadistributionpolicy.go
@@ -19,8 +19,8 @@ func init() {
// Distribution and rebroadcast policy.
type MetadataDistributionPolicy struct {
LexiconTypeID string `json:"$type,omitempty"`
- // allowAiTraining: Whether this content may be used as training data for generative AI models. Absent means undeclared; Streamplace treats undeclared as false and stamps an explicit value into every minted segment.
- AllowAiTraining *bool `json:"allowAiTraining,omitempty"`
+ // allowGenAiTraining: Whether this content may be used as training data for generative AI models. Absent means undeclared; Streamplace treats undeclared as false and stamps an explicit value into every minted segment. Covers generative AI only — non-generative machine learning (e.g. moderation, accessibility, speech models) is not governed by this flag.
+ AllowGenAiTraining *bool `json:"allowGenAiTraining,omitempty"`
// allowedBroadcasters: List of did:webs of the broadcasters you want to allow to distribute your content. "*" allows anyone. Starting a line with a "!" bans that broadcaster.
AllowedBroadcasters []string `json:"allowedBroadcasters,omitempty"`
// deleteAfter: Duration in seconds after which segments should be deleted. Each segment will expire N seconds after its creation time. -1 to allow indefinite archival.
--
2.51.2
From 92220faea4018ad912600e0ebf4fa49662e7fe9c Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Wed, 12 Aug 2026 21:10:27 -0700
Subject: [PATCH 3/9] ci: retrigger flaky builds
linux binaries hit pnpm socket hang-ups; the GitLab test job tripped
the known-flaky TestConcatDemuxBin leak check (passes locally, twice).
Co-Authored-By: Claude Fable 5
--
2.51.2
From 5ba597dbedda8f2fdb84b815ee633301212f5f51 Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Thu, 13 Aug 2026 10:13:03 -0700
Subject: [PATCH 4/9] v0.11.28
---
js/app/package.json | 2 +-
js/components/package.json | 2 +-
js/docs/package.json | 2 +-
lerna.json | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/js/app/package.json b/js/app/package.json
index 04c30502..d6a4c713 100644
--- a/js/app/package.json
+++ b/js/app/package.json
@@ -1,7 +1,7 @@
{
"name": "@streamplace/app",
"main": "./src/entrypoint.tsx",
- "version": "0.11.27",
+ "version": "0.11.28",
"runtimeVersion": "0.10.0",
"scripts": {
"start": "npx expo start -c --port 38081",
diff --git a/js/components/package.json b/js/components/package.json
index a50b0332..b2c19f3f 100644
--- a/js/components/package.json
+++ b/js/components/package.json
@@ -1,6 +1,6 @@
{
"name": "@streamplace/components",
- "version": "0.11.27",
+ "version": "0.11.28",
"description": "Streamplace React (Native) Components",
"main": "dist/index.js",
"types": "src/index.tsx",
diff --git a/js/docs/package.json b/js/docs/package.json
index afa9acc6..6620713d 100644
--- a/js/docs/package.json
+++ b/js/docs/package.json
@@ -1,7 +1,7 @@
{
"name": "streamplace-docs",
"type": "module",
- "version": "0.11.27",
+ "version": "0.11.28",
"scripts": {
"dev": "astro dev --host 0.0.0.0 --port 38082",
"start": "astro dev --host 0.0.0.0 --port 38082",
diff --git a/lerna.json b/lerna.json
index 1294185d..33c62192 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
- "version": "0.11.27",
+ "version": "0.11.28",
"npmClient": "pnpm"
}
--
2.51.2
From 7682f90e0819b40375322e68bf9a77512d52f3bd Mon Sep 17 00:00:00 2001
From: Natalie Bridgers
Date: Fri, 14 Aug 2026 01:54:04 -0500
Subject: [PATCH 5/9] significantly better readme
Signed-off-by: Natalie Bridgers
---
README.md | 45 ++++++++++++++++++++++++++++++++-------------
1 file changed, 32 insertions(+), 13 deletions(-)
diff --git a/README.md b/README.md
index 550f1a1d..7ecf06c7 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,37 @@
-
-
Streamplace
- Solving Video for Everybody Forever
-
- stream.place |
- Documentation |
- Download
-
+
+Streamplace
+Solving Video for Everybody Forever
-## Sponsorship
+Welcome friends! This is the codebase for Streamplace, live video built on the
+[AT Protocol](https://atproto.com), which is the same protocol that powers Bluesky.
-
+Get started fast:
-Streamplace was generously funded by the Livepeer Treasury as part of their
-mission to build the world's open video infrastructure. Check out more at
-[livepeer.org](https://www.livepeer.org/)!
+- **Web: [stream.place](https://stream.place)**
+- **Download: [stream.place/download](https://stream.place/download)**
+- **Docs: [stream.place/docs](https://stream.place/docs)**
+
+## Development Resources
+
+The server is written in Go (`pkg/`, `cmd/`) and the app is a
+[React Native](https://reactnative.dev/) application in TypeScript (`js/`).
+The `place.stream.*` Lexicons live in [`lexicons/`](lexicons/).
+
+You don't _need_ to understand the AT Protocol to work with Streamplace, but
+it helps quite a lot. Learn more at [atproto.com](https://atproto.com/guides/overview).
+
+Good places to start:
+
+- [Quick start: streaming](https://stream.place/docs/guides/start-streaming/quick-start)
+- [Development setup](https://stream.place/docs/guides/start-contributing/streamplace-dev-setup)
+- [Self-hosting](https://stream.place/docs/guides/installing/installing-streamplace)
+- [API reference](https://stream.place/docs/lex-reference/place-stream-defs)
+
+## Contributions
+
+Check for existing [issues](https://github.com/streamplace/streamplace/issues)
+before filing a new one, and open an issue for discussion before submitting a
+large PR. Questions and chat are welcome on
+[Discord](https://discord.stream.place).
--
2.51.2
From 748d28e9a244f3bac4489b346b66c74c53fa6142 Mon Sep 17 00:00:00 2001
From: natalie <22222885+espeon@users.noreply.github.com>
Date: Fri, 14 Aug 2026 02:00:25 -0500
Subject: [PATCH 6/9] Update README.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 7ecf06c7..72da91f5 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ Good places to start:
- [Quick start: streaming](https://stream.place/docs/guides/start-streaming/quick-start)
- [Development setup](https://stream.place/docs/guides/start-contributing/streamplace-dev-setup)
-- [Self-hosting](https://stream.place/docs/guides/installing/installing-streamplace)
+- [Self-hosting](https://stream.place/docs/guides/installing/downloading-streamplace)
- [API reference](https://stream.place/docs/lex-reference/place-stream-defs)
## Contributions
--
2.51.2
From d025a99660131105846b17e138dbc1a0df3285c7 Mon Sep 17 00:00:00 2001
From: Natalie Bridgers
Date: Fri, 14 Aug 2026 02:07:25 -0500
Subject: [PATCH 7/9] should have not been committed rip
Signed-off-by: Natalie Bridgers
---
.impeccable.md | 21 ---------------------
1 file changed, 21 deletions(-)
delete mode 100644 .impeccable.md
diff --git a/.impeccable.md b/.impeccable.md
deleted file mode 100644
index 6c40e953..00000000
--- a/.impeccable.md
+++ /dev/null
@@ -1,21 +0,0 @@
-## Design Context
-
-### Users
-
-Streamers going live and viewers watching streams are the primary audience. Developers building on AT Protocol video infrastructure are a secondary audience. Users open the app to watch live content, chat with communities, or broadcast themselves. The experience should feel like opening a social space, not a utility.
-
-### Brand Personality
-
-Warm, social, inviting. The interface should feel like walking into a friendly room where people are hanging out, not a cold control panel. Approachable without being childish, social without being noisy.
-
-### Aesthetic Direction
-
-Dark-first experience (light mode supported as secondary). The existing pink/rose crystal logo is a strong anchor — lean into warm tones rather than the current default indigo/violet. Avoid generic web3 aesthetics (no gradients, glassmorphism, neon accents on dark backgrounds) and the corporate ad-heavy feel of YouTube. Reference: Arc browser, Figma — distinctive, personality-forward, creative without being chaotic. The UI should have a clear point of view and feel intentionally crafted, not assembled from a template.
-
-### Design Principles
-
-1. **Content-first**: The video and the community come first. UI chrome should recede and let streams and chat breathe.
-2. **Warm competence**: Technically capable without feeling cold. Warm tones, soft edges, human touch in every detail.
-3. **Distinctive identity**: This should never be confused with Twitch, YouTube, or a generic streaming app. Own the pink crystal energy.
-4. **Accessible by default**: Atkinson Hyperlegible is the right foundation. Maintain high contrast ratios, generous touch targets, and clear hierarchy.
-5. **Platform-native feel**: Respect iOS, Android, web, and desktop conventions while maintaining brand consistency across all.
--
2.51.2
From a6212cbde7bbb9a365db33c7ae3809afd5a5ecd0 Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Mon, 24 Aug 2026 18:51:19 -0700
Subject: [PATCH 8/9] intentionally empty commit
--
2.51.2
From 055d7fbfe1ff8cebaa53a09f998e8d0682d037bf Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Mon, 24 Aug 2026 19:03:53 -0700
Subject: [PATCH 9/9] iOS: move AVAudioSession ownership into the
RTCAudioDevice fork
The AppDelegate codemod no longer configures AVAudioSession at boot
(previously .playAndRecord + .allowBluetooth only, i.e. no A2DP, fixed
for the app's lifetime). AUAudioUnitRTCAudioDevice now owns the session
via an AudioSessionCoordinator that derives category/mode/options from
playback vs recording state, so Bluetooth headphones get full-quality
A2DP routing during playback and the device recovers from route changes
and media services resets instead of requiring an app restart.
Also drops the enableStereoOutput line from the ObjC codemod variant:
this fork's WebRTCModuleOptions has no such property, so that path could
never have compiled.
Bumps rtcaudiodevice to streamplace/RTCAudioDevice@38bf554.
Co-Authored-By: Claude Fable 5
---
js/app/package.json | 2 +-
js/config-react-native-webrtc/package.json | 2 +-
.../src/config-react-native-webrtc.ts | 28 ++++---------------
pnpm-lock.yaml | 15 +++++-----
4 files changed, 16 insertions(+), 31 deletions(-)
diff --git a/js/app/package.json b/js/app/package.json
index d6a4c713..e9e64752 100644
--- a/js/app/package.json
+++ b/js/app/package.json
@@ -115,7 +115,7 @@
"react-native-webview": "13.16.0",
"react-use-websocket": "^4.13.0",
"reanimated-color-picker": "^4.0.0",
- "rtcaudiodevice": "git+https://github.com/streamplace/RTCAudioDevice.git#918e08a0f6f0818fb495a0db0b696b44d11d1336",
+ "rtcaudiodevice": "git+https://github.com/streamplace/RTCAudioDevice.git#38bf55484ca14366e4322c565aa4ed025107734b",
"sdp-transform": "^2.15.0",
"stream-http": "^3.2.0",
"streamplace": "workspace:*",
diff --git a/js/config-react-native-webrtc/package.json b/js/config-react-native-webrtc/package.json
index 221aac9e..bea60d59 100644
--- a/js/config-react-native-webrtc/package.json
+++ b/js/config-react-native-webrtc/package.json
@@ -9,7 +9,7 @@
"dependencies": {
"@config-plugins/react-native-webrtc": "10.0.0",
"react-native-webrtc": "git+https://github.com/streamplace/react-native-webrtc.git#74fa32266e3a2fee180f5e01bb8753af2a92d9d3",
- "rtcaudiodevice": "git+https://github.com/streamplace/RTCAudioDevice.git#918e08a0f6f0818fb495a0db0b696b44d11d1336"
+ "rtcaudiodevice": "git+https://github.com/streamplace/RTCAudioDevice.git#38bf55484ca14366e4322c565aa4ed025107734b"
},
"devDependencies": {
"typescript": "~5.3.3"
diff --git a/js/config-react-native-webrtc/src/config-react-native-webrtc.ts b/js/config-react-native-webrtc/src/config-react-native-webrtc.ts
index 7f961f39..98abb574 100644
--- a/js/config-react-native-webrtc/src/config-react-native-webrtc.ts
+++ b/js/config-react-native-webrtc/src/config-react-native-webrtc.ts
@@ -102,23 +102,15 @@ const iosDelegateReplacements = [
to: () => `
self.initialProps = @{};
////RTC PATCH////
- RTCAudioSessionConfiguration* config = [RTCAudioSessionConfiguration webRTCConfiguration];
-
- AVAudioSession * session = [AVAudioSession sharedInstance];
- // Set audio to use phone speaker instead of headset speaker
- [session setCategory:AVAudioSessionCategoryPlayAndRecord
- withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth
- error:nil];
- [session setActive:YES error:nil];
-
+ // AVAudioSession configuration is owned by AUAudioUnitRTCAudioDevice's
+ // AudioSessionCoordinator, which derives it from playback/recording state.
+ // Do not configure the session here.
id device;
device = [[AUAudioUnitRTCAudioDevice alloc] init];
WebRTCModuleOptions *options = [WebRTCModuleOptions sharedInstance];
options.loggingSeverity = RTCLoggingSeverityWarning;
options.audioDevice = device;
- // Enable stereo audio
- options.enableStereoOutput = YES;
////END RTC PATCH////
`,
},
@@ -127,17 +119,9 @@ const iosDelegateReplacements = [
from: " let delegate = ReactNativeDelegate()",
to: () => `
// WebRTC Configuration
- let config = RTCAudioSessionConfiguration.webRTC()
-
- let session = AVAudioSession.sharedInstance()
- do {
- try session.setCategory(.playAndRecord,
- options: [.defaultToSpeaker, .allowBluetooth])
- try session.setActive(true)
- } catch {
- print("Failed to configure audio session: \(error)")
- }
-
+ // AVAudioSession configuration is owned by AUAudioUnitRTCAudioDevice's
+ // AudioSessionCoordinator, which derives it from playback/recording state.
+ // Do not configure the session here.
let device = AUAudioUnitRTCAudioDevice()
let options = WebRTCModuleOptions.sharedInstance()
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 90a93b9e..6741a3a6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -315,8 +315,8 @@ importers:
specifier: ^4.0.0
version: 4.2.0(expo@55.0.6)(react-native-gesture-handler@2.30.0(react-native@0.83.2(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.0.8)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0))(react-native-reanimated@4.2.2(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.2(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.0.8)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0))(react-native@0.83.2(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.0.8)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0))(react-native@0.83.2(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.0.8)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0)
rtcaudiodevice:
- specifier: git+https://github.com/streamplace/RTCAudioDevice.git#918e08a0f6f0818fb495a0db0b696b44d11d1336
- version: https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/918e08a0f6f0818fb495a0db0b696b44d11d1336
+ specifier: git+https://github.com/streamplace/RTCAudioDevice.git#38bf55484ca14366e4322c565aa4ed025107734b
+ version: https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/38bf55484ca14366e4322c565aa4ed025107734b
sdp-transform:
specifier: ^2.15.0
version: 2.15.0
@@ -548,8 +548,8 @@ importers:
specifier: git+https://github.com/streamplace/react-native-webrtc.git#74fa32266e3a2fee180f5e01bb8753af2a92d9d3
version: https://codeload.github.com/streamplace/react-native-webrtc/tar.gz/74fa32266e3a2fee180f5e01bb8753af2a92d9d3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.0.8)(react@19.2.4)(utf-8-validate@5.0.10))
rtcaudiodevice:
- specifier: git+https://github.com/streamplace/RTCAudioDevice.git#918e08a0f6f0818fb495a0db0b696b44d11d1336
- version: https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/918e08a0f6f0818fb495a0db0b696b44d11d1336
+ specifier: git+https://github.com/streamplace/RTCAudioDevice.git#38bf55484ca14366e4322c565aa4ed025107734b
+ version: https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/38bf55484ca14366e4322c565aa4ed025107734b
devDependencies:
typescript:
specifier: ~5.3.3
@@ -8467,6 +8467,7 @@ packages:
eslint@9.39.4:
resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
peerDependencies:
jiti: '*'
@@ -13298,8 +13299,8 @@ packages:
resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==}
engines: {node: 6.* || >= 7.*}
- rtcaudiodevice@https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/918e08a0f6f0818fb495a0db0b696b44d11d1336:
- resolution: {tarball: https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/918e08a0f6f0818fb495a0db0b696b44d11d1336}
+ rtcaudiodevice@https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/38bf55484ca14366e4322c565aa4ed025107734b:
+ resolution: {tarball: https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/38bf55484ca14366e4322c565aa4ed025107734b}
version: 0.0.1
rtl-detect@1.1.2:
@@ -32238,7 +32239,7 @@ snapshots:
rsvp@4.8.5: {}
- rtcaudiodevice@https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/918e08a0f6f0818fb495a0db0b696b44d11d1336: {}
+ rtcaudiodevice@https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/38bf55484ca14366e4322c565aa4ed025107734b: {}
rtl-detect@1.1.2: {}