···1+import { Platform } from "react-native";
2+3+export default function clearQueryParams(par = ["iss", "state", "code"]) {
4+ if (Platform.OS !== "web") {
5+ return;
6+ }
7+ const u = new URL(document.location.href);
8+ const params = new URLSearchParams(u.search);
9+ if (u.search === "") {
10+ return;
11+ }
12+ par.forEach((p) => params.delete(p));
13+ u.search = params.toString();
14+ window.history.replaceState(null, "", u.toString());
15+}
+3-6
js/atproto-oauth-client-react-native/README.md
···87forwarded the port with `adb reverse`. For testing on iOS hardware, you'll
88instead need to set up TLS.
8990-[react-native-quick-crypto]:
91- https://github.com/margelo/react-native-quick-crypto
92[expo-sqlite]: https://docs.expo.dev/versions/latest/sdk/sqlite/
93-[README]:
94- https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-browser
95-[example]:
96- https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-browser-example
···87forwarded the port with `adb reverse`. For testing on iOS hardware, you'll
88instead need to set up TLS.
8990+[react-native-quick-crypto]: https://github.com/margelo/react-native-quick-crypto
091[expo-sqlite]: https://docs.expo.dev/versions/latest/sdk/sqlite/
92+[README]: https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-browser
93+[example]: https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-browser-example
00
···1+censored-text-hide = Hide Text
2+censored-text-reveal = Reveal Text
3+censored-text-blocked-with-reasons = This text was blocked because of these reasons: { $reasons }
4+censored-text-blocked-unknown = This text was blocked for an unknown reason
5+6+category-discriminatory = Discriminatory content
7+category-sexually-explicit = Sexually explicit content
8+category-profanity = Profanity
+8
js/components/locales/es-ES/chat.ftl
···00000000
···1+censored-text-hide = Ocultar texto
2+censored-text-reveal = Revelar texto
3+censored-text-blocked-with-reasons = Este texto fue bloqueado por estas razones: { $reasons }
4+censored-text-blocked-unknown = Este texto fue bloqueado por un motivo desconocido
5+6+category-discriminatory = Contenido discriminatorio
7+category-sexually-explicit = Contenido sexualmente explícito
8+category-profanity = Blasfemia
+8
js/components/locales/fr-FR/chat.ftl
···00000000
···1+censored-text-hide = Masquer le texte
2+censored-text-reveal = Révéler le texte
3+censored-text-blocked-with-reasons = Ce texte a été bloqué pour ces raisons : { $reasons }
4+censored-text-blocked-unknown = Ce texte a été bloqué pour une raison inconnue
5+6+category-discriminatory = Contenu discriminatoire
7+category-sexually-explicit = Contenu sexuellement explicite
8+category-profanity = Blasphème
+8
js/components/locales/pt-BR/chat.ftl
···00000000
···1+censored-text-hide = Ocultar texto
2+censored-text-reveal = Revelar texto
3+censored-text-blocked-with-reasons = Este texto foi bloqueado por estes motivos: { $reasons }
4+censored-text-blocked-unknown = Este texto foi bloqueado por um motivo desconhecido
5+6+category-discriminatory = Conteúdo discriminatório
7+category-sexually-explicit = Conteúdo sexualmente explícito
8+category-profanity = Profanidade
···1// barrel file :)
02export * from "./useAvatars";
3export * from "./useCameraToggle";
4export * from "./useDocumentTitle";
···1// barrel file :)
2+export * from "./useAQState";
3export * from "./useAvatars";
4export * from "./useCameraToggle";
5export * from "./useDocumentTitle";
···2// Metro will use this file for React Native builds
34// Import all translations directly so they're bundled into the app
05import enUSCommon from "../../public/locales/en-US/common.json";
6import enUSSettings from "../../public/locales/en-US/settings.json";
07import esESCommon from "../../public/locales/es-ES/common.json";
8import esESSettings from "../../public/locales/es-ES/settings.json";
09import frFRCommon from "../../public/locales/fr-FR/common.json";
10import frFRSettings from "../../public/locales/fr-FR/settings.json";
011import ptBRCommon from "../../public/locales/pt-BR/common.json";
12import ptBRSettings from "../../public/locales/pt-BR/settings.json";
013import zhHantCommon from "../../public/locales/zh-Hant/common.json";
14import zhHantSettings from "../../public/locales/zh-Hant/settings.json";
1516const translationMap: Record<string, any> = {
017 "en-US/common": enUSCommon,
18 "en-US/settings": enUSSettings,
019 "pt-BR/common": ptBRCommon,
20 "pt-BR/settings": ptBRSettings,
021 "es-ES/common": esESCommon,
22 "es-ES/settings": esESSettings,
023 "zh-Hant/common": zhHantCommon,
24 "zh-Hant/settings": zhHantSettings,
025 "fr-FR/common": frFRCommon,
26 "fr-FR/settings": frFRSettings,
27};
···2// Metro will use this file for React Native builds
34// Import all translations directly so they're bundled into the app
5+import enUSChat from "../../public/locales/en-US/chat.json";
6import enUSCommon from "../../public/locales/en-US/common.json";
7import enUSSettings from "../../public/locales/en-US/settings.json";
8+import esESChat from "../../public/locales/es-ES/chat.json";
9import esESCommon from "../../public/locales/es-ES/common.json";
10import esESSettings from "../../public/locales/es-ES/settings.json";
11+import frFRChat from "../../public/locales/fr-FR/chat.json";
12import frFRCommon from "../../public/locales/fr-FR/common.json";
13import frFRSettings from "../../public/locales/fr-FR/settings.json";
14+import ptBRChat from "../../public/locales/pt-BR/chat.json";
15import ptBRCommon from "../../public/locales/pt-BR/common.json";
16import ptBRSettings from "../../public/locales/pt-BR/settings.json";
17+import zhHantChat from "../../public/locales/zh-Hant/chat.json";
18import zhHantCommon from "../../public/locales/zh-Hant/common.json";
19import zhHantSettings from "../../public/locales/zh-Hant/settings.json";
2021const translationMap: Record<string, any> = {
22+ "en-US/chat": enUSChat,
23 "en-US/common": enUSCommon,
24 "en-US/settings": enUSSettings,
25+ "pt-BR/chat": ptBRChat,
26 "pt-BR/common": ptBRCommon,
27 "pt-BR/settings": ptBRSettings,
28+ "es-ES/chat": esESChat,
29 "es-ES/common": esESCommon,
30 "es-ES/settings": esESSettings,
31+ "zh-Hant/chat": zhHantChat,
32 "zh-Hant/common": zhHantCommon,
33 "zh-Hant/settings": zhHantSettings,
34+ "fr-FR/chat": frFRChat,
35 "fr-FR/common": frFRCommon,
36 "fr-FR/settings": frFRSettings,
37};
+1-1
js/components/src/i18n/i18next-config.ts
···116117export const I18NEXT_CONFIG = {
118 lng: LOCALE,
119- ns: ["common", "settings"], // Common should be first as it's most frequently used
120 defaultNS: "common",
121 interpolation: {
122 escapeValue: false, // React already safes from XSS
···116117export const I18NEXT_CONFIG = {
118 lng: LOCALE,
119+ ns: ["common", "settings", "chat"], // Common should be first as it's most frequently used
120 defaultNS: "common",
121 interpolation: {
122 escapeValue: false, // React already safes from XSS
+1
js/components/src/index.tsx
···3334export * from "./components/chat/chat";
35export * from "./components/chat/chat-box";
036export * from "./components/chat/system-message";
37export * from "./components/chat/update-stream-title-dialog";
38export { default as VideoRetry } from "./components/mobile-player/video-retry";
···3334export * from "./components/chat/chat";
35export * from "./components/chat/chat-box";
36+export * from "./components/chat/chat-settings";
37export * from "./components/chat/system-message";
38export * from "./components/chat/update-stream-title-dialog";
39export { default as VideoRetry } from "./components/mobile-player/video-retry";
+3
js/components/src/player-store/player-state.tsx
···63 ingestAutoStart?: boolean;
64 setIngestAutoStart?: (autoStart: boolean) => void;
6500066 /** Timestamp (number) when ingest started, or null if not started */
67 ingestStarted: number | null;
68
···63 ingestAutoStart?: boolean;
64 setIngestAutoStart?: (autoStart: boolean) => void;
6566+ /** stop ingest process, again with a slight delay to allow UI to update */
67+ stopIngest: () => void;
68+69 /** Timestamp (number) when ingest started, or null if not started */
70 ingestStarted: number | null;
71
···25 });
26};
270000000000000000000000028// hook to fetch broadcaster DID (unauthenticated)
29export function useFetchBroadcasterDID() {
30 const streamplaceAgent = usePossiblyUnauthedPDSAgent();
31 const store = getStreamplaceStoreFromContext();
000000000000000000000000000000003233 return useCallback(async () => {
34 try {
···140141// hook to get a specific branding asset by key
142export function useBrandingAsset(key: string): BrandingAsset | undefined {
143- return useStreamplaceStore((state) => state.branding?.[key]);
0000144}
145146// convenience hook for main logo
···25 });
26};
2728+const PropsInHeader = [
29+ "siteTitle",
30+ "siteDescription",
31+ "primaryColor",
32+ "accentColor",
33+ "defaultStreamer",
34+ "mainLogo",
35+ "favicon",
36+ "sidebarBg",
37+ "legalLinks",
38+];
39+40+function getMetaContent(key: string): BrandingAsset | null {
41+ if (typeof window === "undefined" || !window.document) return null;
42+ const meta = document.querySelector(`meta[name="internal-brand:${key}`);
43+ if (meta && meta.getAttribute("content")) {
44+ let content = meta.getAttribute("content");
45+ if (content) return JSON.parse(content) as BrandingAsset;
46+ }
47+48+ return null;
49+}
50+51// hook to fetch broadcaster DID (unauthenticated)
52export function useFetchBroadcasterDID() {
53 const streamplaceAgent = usePossiblyUnauthedPDSAgent();
54 const store = getStreamplaceStoreFromContext();
55+56+ // prefetch from meta records, if on web
57+ useEffect(() => {
58+ if (typeof window !== "undefined" && window.document) {
59+ try {
60+ const metaRecords = PropsInHeader.reduce(
61+ (acc, key) => {
62+ const meta = document.querySelector(
63+ `meta[name="internal-brand:${key}`,
64+ );
65+ // hrmmmmmmmmmmmm
66+ if (meta && meta.getAttribute("content")) {
67+ let content = meta.getAttribute("content");
68+ if (content) acc[key] = JSON.parse(content) as BrandingAsset;
69+ }
70+ return acc;
71+ },
72+ {} as Record<string, BrandingAsset>,
73+ );
74+75+ console.log("Found meta records for broadcaster DID:", metaRecords);
76+ // filter out all non-text values, can get on second fetch?
77+ for (const key of Object.keys(metaRecords)) {
78+ if (metaRecords[key].mimeType != "text/plain") {
79+ delete metaRecords[key];
80+ }
81+ }
82+ } catch (e) {
83+ console.warn("Failed to parse broadcaster DID from meta tags", e);
84+ }
85+ }
86+ }, []);
8788 return useCallback(async () => {
89 try {
···195196// hook to get a specific branding asset by key
197export function useBrandingAsset(key: string): BrandingAsset | undefined {
198+ return (
199+ useStreamplaceStore((state) => state.branding?.[key]) ||
200+ getMetaContent(key) ||
201+ undefined
202+ );
203}
204205// convenience hook for main logo
···1+---
2+import { Card, CardGrid } from "@astrojs/starlight/components";
3+4+interface Props {
5+ searchPlaceholder?: string;
6+}
7+---
8+9+<div class="helpdesk">
10+11+ <h2>How can we help?</h2>
12+ <p>Search the knowledge base, or check out topics below.</p>
13+14+ <CardGrid>
15+ <Card title="Getting Started" icon="rocket">
16+ <p>New to Streamplace? Start here to set up your first stream.</p>
17+ <ul>
18+ <li><a href="/docs/guides/start-streaming/quick-start">Quick start guide</a></li>
19+ <li><a href="/docs/guides/start-streaming/obs">Stream with OBS</a></li>
20+ </ul>
21+ </Card>
22+23+ <Card title="Developers & Self-Hosters" icon="laptop">
24+ <p>Building with Streamplace or running your own node?</p>
25+ <ul>
26+ <li><a href="/docs/developers">Developer documentation</a></li>
27+ </ul>
28+ </Card>
29+ </CardGrid>
30+</div>
31+32+<style>
33+ .helpdesk {
34+ margin: 0 auto;
35+ }
36+37+ .helpdesk-search {
38+ margin-bottom: 2rem;
39+ }
40+41+ .search-input {
42+ width: 100%;
43+ padding: 1rem 1.5rem;
44+ font-size: 1.125rem;
45+ border: 2px solid var(--sl-color-gray-5);
46+ border-radius: 0.5rem;
47+ background: var(--sl-color-bg);
48+ color: var(--sl-color-text);
49+ transition: border-color 0.2s;
50+ }
51+52+ .search-input:focus {
53+ outline: none;
54+ border-color: var(--sl-color-accent);
55+ }
56+57+ .helpdesk h2 {
58+ margin-bottom: 1.5rem;
59+ }
60+</style>
+1-2
js/docs/src/content/docs/components/custom_ui.md
···1---
2title: Creating your own player UI
3-description:
4- How to set up your player UI with components from @streamplace/components.
5---
67# Building a Custom Player UI
···1---
2title: Creating your own player UI
3+description: How to set up your player UI with components from @streamplace/components.
04---
56# Building a Custom Player UI
+40
js/docs/src/content/docs/developers.mdx
···0000000000000000000000000000000000000000
···1+---
2+title: Developers & Self-Hosters
3+description: Build with Streamplace or run your own infrastructure.
4+template: doc
5+---
6+7+import { Card, CardGrid } from "@astrojs/starlight/components";
8+9+## Learn how to deploy, or contribute to Streamplace.
10+11+<br />
12+13+<CardGrid stagger>
14+ <Card title="Building an Application" icon="laptop">
15+ Integrate live video into your project. - [API
16+ reference](/docs/lex-reference/place-stream-defs) - [Our component
17+ library](/docs/components/custom_ui/)
18+ </Card>
19+20+{" "}
21+22+<Card title="Self-Hosting" icon="seti:config">
23+ Run your own Streamplace infrastructure. - [Installation
24+ guide](/docs/guides/installing/installing-streamplace)
25+</Card>
26+27+{" "}
28+29+<Card title="Contributing" icon="github">
30+ Help improve Streamplace. - [Development
31+ setup](/docs/guides/streamplace-dev-setup) - [Video
32+ signing](/docs/video-metadata/intro/)
33+</Card>
34+35+ <Card title="Support & Community" icon="information">
36+ Get help and connect with other developers. - [GitHub
37+ issues](https://github.com/streamplace/streamplace/issues) - [Discord
38+ community](https://discord.stream.place)
39+ </Card>
40+</CardGrid>
+3-1
js/docs/src/content/docs/features/danmu.md
···3description: Add flying bullet-style chat comments to the player, or your stream
4---
56-:::note This feature is experimental and may change in future releases. :::
0078[Danmu (or Danmaku)](https://en.wikipedia.org/wiki/Danmaku_subtitling) (弹幕,
9"bullet curtain") is a comment style where messages fly across the video
···3description: Add flying bullet-style chat comments to the player, or your stream
4---
56+:::note
7+This feature is experimental and may change in future releases.
8+:::
910[Danmu (or Danmaku)](https://en.wikipedia.org/wiki/Danmaku_subtitling) (弹幕,
11"bullet curtain") is a comment style where messages fly across the video
+27
js/docs/src/content/docs/features/embed.md
···000000000000000000000000000
···1+---
2+title: Embedding your livestream
3+description: How to embed your livestream on your website, blog, etc.
4+---
5+6+Streamplace provides an easy way to embed your livestream on any website or
7+blog.
8+9+You can access the embedded livestream page by putting `/embed` in the URL of
10+your livestream. For example, if your livestream URL is
11+`https://stream.place/iame.li`, the embed URL will be
12+`https://stream.place/embed/iame.li`.
13+14+You can use the following HTML snippet to embed your livestream:
15+16+```html
17+<iframe
18+ src="https://stream.place/embed/your-handle"
19+ width="560"
20+ height="315"
21+ frameborder="0"
22+ allowfullscreen
23+></iframe>
24+```
25+26+Alternatively, you can use the share sheet located on your livestream page.
27+Click the "Share" button, and you'll find the embed code ready to copy.
···1+---
2+title: Multistreaming
3+description: Forward your Streamplace stream to other providers.
4+---
5+6+:::note
7+This guide isn't about setting up Streamplace as an OBS destination. See [OBS Multistreaming to Streamplace](/docs/guides/start-streaming/obs-multistreaming/) for information on that.
8+:::
9+10+Multistreaming lets you forward your Streamplace stream to multiple platforms at the same time. Instead of streaming only to Streamplace, you can forward your stream to any platform that accepts RTMP input.
11+12+## Setting up multistream targets
13+14+1. Go to **Settings** > **Streaming** > **Multistream Targets**
15+2. Click **Create Multistream Target**
16+3. Enter the RTMP or RTMPS URL from your destination platform
17+4. Optionally give it a name to identify it later
18+5. Click **Create**
19+20+### Finding your multistream URL
21+22+Different platforms will provide their own RTMP URLs. Some common examples:
23+24+- **YouTube Live**: Format `rtmp://a.rtmp.youtube.com/live2/your-stream-key`
25+ - Find your stream key at https://studio.youtube.com/channel/UC/livestreaming (click the copy icon in the top right corner of the 'connect your encoder to go live' box)
26+- **Twitch**: Format `rtmp://usw20.contribute.live-video.net/app/your-stream-key`
27+ - You can get a valid RTMPS url at https://help.twitch.tv/s/twitch-ingest-recommendation
28+ - Find your stream key at https://dashboard.twitch.tv/settings/stream (your 'primary stream key')
29+30+:::note
31+Your stream key should automatically be hidden once you confirm. Make sure you've entered it correctly!
32+:::
33+34+## Managing targets during a stream
35+36+When you're live, you can see all your multistream targets on the Live Dashboard with their current status:
37+38+- **Green (Active)**: Successfully streaming to this target
39+- **Yellow (Pending)**: Connecting to this target
40+- **Red (Error)**: Connection failed; check your URL and credentials
41+- **Gray (Inactive)**: This target is disabled
42+43+You can toggle any target on or off with the switch next to its name. Changes take effect immediately.
44+45+## Limits
46+47+- **Maximum targets**: 100 total per account
48+- **Maximum active targets**: 5 simultaneous streams
49+50+### Credits
51+52+A portion of this documentation was taken from [ndroo.tv](https://bsky.app/profile/ndroo.tv)'s [guide on Streamplace](https://ndroo.tv/streamplace.html#2-configuring-your-account).
···1+---
2+title: Discord Webhooks
3+description: Configure Discord webhooks for livestream announcements and chat
4+sidebar:
5+ order: 30
6+---
7+8+Streamplace supports Discord webhooks for receiving livestream
9+notifications and chat messages. You can create, manage, and configure webhooks
10+to customize how events are delivered to your Discord channels.
11+12+## Webhook Events
13+14+You can configure webhooks to listen for specific events. For right now, the
15+following events are supported:
16+17+- `Chat`: Triggered when a chat message is sent.
18+- `Livestream`: Triggered when a livestream starts.
19+20+## Creating a Webhook
21+22+To create a webhook, go to the "Settings" page of the Streamplace web app, then
23+navigate to the "Webhooks" section. Click on "Create Webhook". The following
24+fields are required:
25+26+- Name: Webhook URL. For example,
27+ `https://discord.com/api/webhooks/{webhook.id}/{webhook.token}`
28+- Events: Select the events you want to subscribe to (e.g., `Chat Messages`,
29+ `Livestream Started`). `Livestream Started` is pre-checked by default.
30+31+We'd recommend also filling out these optional fields:
32+33+- Name: A name for the webhook (e.g., "Discord Livestream Notifications") that
34+ you can remember.
35+- Description: A description of what this webhook is for (e.g., "Sends
36+ livestream start notifications to Discord channel").
37+- Prefix: A prefix to add to each message sent by this webhook (e.g.,
38+ "[Streamplace] "). Will apply to both Chat and Livestream events!
39+- Suffix: A suffix to add to each message sent by this webhook (e.g., "is now
40+ live!"). Will apply to both Chat and Livestream events!
41+- Text replacements: A list of text replacements to apply to chat messages sent
42+ by this webhook. Each replacement consists of a "from" string and a "to"
43+ string. For example, you could replace all instances of "foo" with "bar".
44+45+After filling out the form, click "Create" to save your webhook. You should see
46+it listed in the "Webhooks" section.
47+48+## Updating a Webhook
49+50+To update a webhook, go to the "Settings" page of the Streamplace web app, then
51+navigate to the "Webhooks" section. Find the webhook you want to update and
52+click on the "pen" icon next to it. This will open the webhook edit form, where
53+you can modify the fields as needed. After making your changes, click "Update"
54+to save your changes.
55+56+## Deleting a Webhook
57+58+To delete a webhook, go to the "Settings" page of the Streamplace web app, then
59+navigate to the "Webhooks" section. Find the webhook you want to delete and
60+click on the "trash" icon next to it. A confirmation dialog will appear; click
61+"Delete" to confirm. The webhook will be removed from the list.
62+63+## Recommendations
64+65+We'd recommend:
66+67+- Creating separate Discord channels for livestream notifications and chat
68+ messages to keep them organized.
69+ - If you want to have one webhook for both chat and livestream events, you can
70+ create multiple webhooks with the same URL but different event subscriptions
71+ and prefixes/suffixes/replacements.
72+- Testing your webhook by starting a livestream or sending a chat message to
73+ ensure that notifications are being sent correctly.
74+75+## API Documentation
76+77+See these endpoint pages:
78+79+- [Create Webhook](/docs/api/operations/placestreamservercreatewebhook)
80+- [Get Webhook](/docs/api/operations/placestreamservergetwebhook)
81+- [List Webhooks](/docs/api/operations/placestreamserverlistwebhooks)
82+- [Update Webhook](/docs/api/operations/placestreamserverupdatewebhook)
83+- [Delete Webhook](/docs/api/operations/placestreamserverdeletewebhook)
···1----
2-title: Discord Webhooks
3-description: Configure Discord webhooks for livestream announcements and chat
4-sidebar:
5- order: 30
6----
7-8-Streamplace supports Discord webhook integration for receiving livestream
9-notifications and chat messages. You can create, manage, and configure webhooks
10-to customize how events are delivered to your Discord channels.
11-12-## Webhook Events
13-14-You can configure webhooks to listen for specific events. For right now, the
15-following events are supported:
16-17-- `Chat`: Triggered when a chat message is sent.
18-- `Livestream`: Triggered when a livestream starts.
19-20-## Creating a Webhook
21-22-To create a webhook, go to the "Settings" page of the Streamplace web app, then
23-navigate to the "Webhooks" section. Click on "Create Webhook". The following
24-fields are required:
25-26-- Name: Webhook URL. For example,
27- `https://discord.com/api/webhooks/{webhook.id}/{webhook.token}`
28-- Events: Select the events you want to subscribe to (e.g., `Chat Messages`,
29- `Livestream Started`). `Livestream Started` is pre-checked by default.
30-31-We'd recommend also filling out these optional fields:
32-33-- Name: A name for the webhook (e.g., "Discord Livestream Notifications") that
34- you can remember.
35-- Description: A description of what this webhook is for (e.g., "Sends
36- livestream start notifications to Discord channel").
37-- Prefix: A prefix to add to each message sent by this webhook (e.g.,
38- "[Streamplace] "). Will apply to both Chat and Livestream events!
39-- Suffix: A suffix to add to each message sent by this webhook (e.g., "is now
40- live!"). Will apply to both Chat and Livestream events!
41-- Text replacements: A list of text replacements to apply to chat messages sent
42- by this webhook. Each replacement consists of a "from" string and a "to"
43- string. For example, you could replace all instances of "foo" with "bar".
44-45-After filling out the form, click "Create" to save your webhook. You should see
46-it listed in the "Webhooks" section.
47-48-## Updating a Webhook
49-50-To update a webhook, go to the "Settings" page of the Streamplace web app, then
51-navigate to the "Webhooks" section. Find the webhook you want to update and
52-click on the "pen" icon next to it. This will open the webhook edit form, where
53-you can modify the fields as needed. After making your changes, click "Update"
54-to save your changes.
55-56-## Deleting a Webhook
57-58-To delete a webhook, go to the "Settings" page of the Streamplace web app, then
59-navigate to the "Webhooks" section. Find the webhook you want to delete and
60-click on the "trash" icon next to it. A confirmation dialog will appear; click
61-"Delete" to confirm. The webhook will be removed from the list.
62-63-## Recommendations
64-65-We'd recommend:
66-67-- Creating separate Discord channels for livestream notifications and chat
68- messages to keep them organized.
69- - If you want to have one webhook for both chat and livestream events, you can
70- create multiple webhooks with the same URL but different event subscriptions
71- and prefixes/suffixes/replacements.
72-- Testing your webhook by starting a livestream or sending a chat message to
73- ensure that notifications are being sent correctly.
74-75-## API Documentation
76-77-See these endpoint pages:
78-79-- [Create Webhook](/docs/api/operations/placestreamservercreatewebhook)
80-- [Get Webhook](/docs/api/operations/placestreamservergetwebhook)
81-- [List Webhooks](/docs/api/operations/placestreamserverlistwebhooks)
82-- [Update Webhook](/docs/api/operations/placestreamserverupdatewebhook)
83-- [Delete Webhook](/docs/api/operations/placestreamserverdeletewebhook)
···1----
2-title: Embedding your livestream
3-description: How to embed your livestream on your website, blog, etc.
4----
5-6-Streamplace provides an easy way to embed your livestream on any website or
7-blog.
8-9-You can access the embedded livestream page by putting `/embed` in the URL of
10-your livestream. For example, if your livestream URL is
11-`https://stream.place/iame.li`, the embed URL will be
12-`https://stream.place/embed/iame.li`.
13-14-You can use the following HTML snippet to embed your livestream:
15-16-```html
17-<iframe
18- src="https://stream.place/embed/your-handle"
19- width="560"
20- height="315"
21- frameborder="0"
22- allowfullscreen
23-></iframe>
24-```
25-26-Alternatively, you can use the share sheet located on your livestream page.
27-Click the "Share" button, and you'll find the embed code ready to copy.
···1---
2-title: OBS Multistreaming with Streamplace
3description:
4 Configure OBS for multistreaming to Streamplace and other platforms using the
5 obs-multi-rtmp plugin.
6sidebar:
7 order: 20
8---
000000910This guide explains how to configure Open Broadcaster Software (OBS) for
11simultaneous streaming to Streamplace and other platforms using the
···1---
2+title: OBS Multistreaming to Streamplace
3description:
4 Configure OBS for multistreaming to Streamplace and other platforms using the
5 obs-multi-rtmp plugin.
6sidebar:
7 order: 20
8---
9+10+:::note
11+This guide is not about the multistreaming feature. Check
12+[the multistreaming guide](/docs/features/multistreaming) out for more
13+information.
14+:::
1516This guide explains how to configure Open Broadcaster Software (OBS) for
17simultaneous streaming to Streamplace and other platforms using the
···6667- Video Encoder: x264/h264 (**must** be an x/h.264 encoder)
68- Rate Control: `CBR`
69-- Keyframe Interval: `1s`
70 - This is _one keyframe per second_
71 - In some situations (e.g. 'keyframe interval (**frames**)'), this should be
72 set to your FPS.
73- x264 Options: `bframes=0`
74 - If available, there also may be a 'bframes' checkbox which should **NOT** be
75 checked
00007677### 3. Announce your stream
78···90 - [OBS Multistreaming Guide](guides/obs-multistreaming)
91922. [**Aitum Multistream Plugin**](https://aitum.tv/products/multi)
0009394## Best Practices
95
···6667- Video Encoder: x264/h264 (**must** be an x/h.264 encoder)
68- Rate Control: `CBR`
69+- Keyframe Interval: `1s` (or anything less than once every ~7s)
70 - This is _one keyframe per second_
71 - In some situations (e.g. 'keyframe interval (**frames**)'), this should be
72 set to your FPS.
73- x264 Options: `bframes=0`
74 - If available, there also may be a 'bframes' checkbox which should **NOT** be
75 checked
76+77+:::caution
78+These last two options are very important! Your viewers' experience may be choppy or otherwise subpar if you don't have them correct.
79+:::
8081### 3. Announce your stream
82···94 - [OBS Multistreaming Guide](guides/obs-multistreaming)
95962. [**Aitum Multistream Plugin**](https://aitum.tv/products/multi)
97+98+Alternatively, you can
99+[multistream through Streamplace itself.](/docs/features/multistreaming)
100101## Best Practices
102
···1+---
2+title: Quick Start
3+description: Get up and streaming on Streamplace quickly.
4+sidebar:
5+ order: 1
6+---
7+8+This guide gets you from zero to streaming. If you get stuck, check out the full [OBS setup guide](/docs/guides/start-streaming/obs).
9+10+:::tip
11+You will want to check out our [community guidelines](https://blog.stream.place/3mcqwibo4ks2w) first for guidance on what you can and cannot do on Streamplace.
12+:::
13+14+## So, what is Streamplace?
15+16+Streamplace is a video streaming service built on top of the AT Protocol (Authenticated Transfer Protocol), the same protocol Bluesky is built on.
17+18+## Step 1: Create your account
19+20+1. Go to [stream.place](https://stream.place)
21+2. Click "Sign in" in the top right.
22+3. Use your Atmosphere credentials to log in (ex. your Bluesky handle)
23+ - You'll need to use your actual password here - we're using OAuth so you enter your password on your PDS. We do not receive your password at all.
24+4. You're done! Your stream profile is live at `stream.place/your-handle`
25+26+## Step 2: Get your stream key
27+28+1. Click **Live Dashboard** (or go to [stream.place/dashboard](https://stream.place/dashboard))
29+2. Click **Stream from OBS**
30+3. Click **Generate Stream Key**
31+4. Your key is copied to clipboard automatically
32+33+Keep this key private. It's like a password, but for your stream.
34+35+## Step 3: Configure OBS
36+37+Open OBS and go to **Settings → Stream**:
38+39+- **Service**: `Custom...`
40+- **Server**: `rtmps://stream.place:1935/live`
41+- **Stream Key**: Paste what you copied in Step 2
42+43+Then go to **Settings → Output → Streaming**:
44+45+- **Video Encoder**: `libx264` (or `NVIDIA NVENC H.264` if you have an NVIDIA GPU)
46+- **Rate Control**: `CBR`
47+- **Bitrate**: `6000` Kbps (adjust down if you drop frames)
48+- **Keyframe Interval**: `1`
49+- **x264 Options**: `bframes=0`. If there's a 'bframes' option, you'll want to have that at '0' or unchecked.
50+51+:::caution
52+These last two options are very important! Your viewers' experience may be choppy or otherwise subpar if you don't have them correct.
53+:::
54+55+## Step 4: Go live
56+57+1. In OBS, click **Start Streaming**
58+2. Go back to the Live Dashboard at stream.place
59+3. Fill in your stream title and optionally pick a thumbnail8
60+4. If needed, turn on content warnings. ("Metadata" tab in Stream Settings)
61+5. Click **Announce Livestream**
62+6. Your stream is now live and visible to the world!
63+64+## Next steps
65+66+- **Customize your chat**: Change your name color in Settings > Account
67+- **Stream to other platforms too**: Set your Twitch/YouTube URLs in Settings > Multistream Targets to push your stream there automatically. See the [Multistreaming guide](/docs/features/multistreaming) for more information
68+- **Improve stream quality**: See the [OBS guide](/docs/guides/start-streaming/obs) for encoder settings and troubleshooting
69+- **Join the Discord!**: If you need any help, or just want to chat, check out our discord at https://discord.stream.place.
70+71+### Credits
72+73+A portion of this documentation was taken from [ndroo.tv](https://bsky.app/profile/ndroo.tv)'s excellent [guide on Streamplace](https://ndroo.tv/streamplace.html#2-configuring-your-account).
+2-32
js/docs/src/content/docs/index.mdx
···2title: Welcome to Streamplace!
3description: Begin your development journey with the Streamplace documentation.
4template: doc
5-hero:
6- tagline: Solve live video for your project with Streamplace.
7- image:
8- file: ../../assets/cube.png
9- alt: Streamplace logo. A pink 3d box viewed from a top corner.
10- actions:
11- - text: Get Started
12- link: /docs/guides/start-streaming/obs
13- icon: right-arrow
14- - text: Visit Streamplace
15- link: /
16- icon: external
17- variant: minimal
18---
1920-import { Card, CardGrid } from "@astrojs/starlight/components";
21-22-## Next Steps
2324-<CardGrid>
25- <Card title="Read the Docs" icon="open-book">
26- Learn how to start streaming with
27- [Streamplace](/docs/guides/start-streaming/obs).
28- </Card>
29- <Card title="Install Streamplace" icon="download">
30- [Run your own Streamplace
31- node](/docs/guides/installing/installing-streamplace).
32- </Card>
33- <Card title="API Reference" icon="document">
34- Explore the [Lexicon API reference](/docs/lex-reference/place-stream-defs).
35- </Card>
36- <Card title="Developer Setup" icon="setting">
37- Set up your [development environment](/docs/guides/streamplace-dev-setup).
38- </Card>
39-</CardGrid>
···2title: Welcome to Streamplace!
3description: Begin your development journey with the Streamplace documentation.
4template: doc
00000000000005---
67+import HelpDesk from "../../components/HelpDesk.astro";
0089+<HelpDesk />
000000000000000
···28- **Description:** Raw blob data with appropriate content-type
29- **Schema:**
3031-_Schema not defined._ **Possible Errors:**
03233- `BrandingNotFound`: The requested branding asset does not exist
34
···28- **Description:** Raw blob data with appropriate content-type
29- **Schema:**
3031+_Schema not defined._
32+**Possible Errors:**
3334- `BrandingNotFound`: The requested branding asset does not exist
35
···1314**Type:** `record`
1516-Record indicating a livestream is published and available for replication at a
17-given address. By convention, the record key is streamer::server
1819**Record Key:** `any`
20
···1314**Type:** `record`
1516+Record indicating a livestream is published and available for replication at a given address. By convention, the record key is streamer::server
01718**Record Key:** `any`
19
···1314**Type:** `record`
1516-Record created by a Streamplace broadcaster to indicate that they will be
17-replicating a livestream. NYI
1819**Record Key:** `tid`
20
···1314**Type:** `record`
1516+Record created by a Streamplace broadcaster to indicate that they will be replicating a livestream. NYI
01718**Record Key:** `tid`
19
···1314**Type:** `query`
1516-Find actor suggestions for a prefix search term. Expected use is for
17-auto-completion during text field entry.
1819**Parameters:**
20
···1314**Type:** `query`
1516+Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry.
01718**Parameters:**
19
···1314**Type:** `record`
1516-Default metadata record for livestream including content warnings, rights, and
17-distribution policy
1819**Record Key:** `literal:self`
20
···1314**Type:** `record`
1516+Default metadata record for livestream including content warnings, rights, and distribution policy
01718**Record Key:** `literal:self`
19
···3334**Type:** `token`
3536-All rights reserved to the creator — others cannot use, modify, or share without
37-explicit authorization.
3839---
40···4445**Type:** `token`
4647-Public domain dedication. You waive all copyright and related rights where
48-possible. Others may copy, modify, distribute, or perform your work for any
49-purpose without attribution.
5051---
52···5657**Type:** `token`
5859-Attribution required. Others may copy, distribute, remix, and build upon your
60-work, even commercially, if they credit you.
6162---
63···6768**Type:** `token`
6970-Attribution + share-alike. Others may adapt and build upon your work, even
71-commercially, if they credit you and license their new creations under identical
72-terms.
7374---
75···7980**Type:** `token`
8182-Attribution + non-commercial. Others may adapt and build upon your work for
83-non-commercial purposes only, and must credit you.
8485---
86···9091**Type:** `token`
9293-Attribution + non-commercial + share-alike. Others may adapt and build upon your
94-work for non-commercial purposes only, must credit you, and must license their
95-new creations under identical terms.
9697---
98···102103**Type:** `token`
104105-Attribution + no derivatives. Others may reuse your work, even commercially, but
106-it must remain unchanged and you must be credited.
107108---
109···113114**Type:** `token`
115116-Attribution + non-commercial + no derivatives. Others may download and share
117-your work with credit, but cannot change it or use it commercially.
118119---
120
···3334**Type:** `token`
3536+All rights reserved to the creator — others cannot use, modify, or share without explicit authorization.
03738---
39···4344**Type:** `token`
4546+Public domain dedication. You waive all copyright and related rights where possible. Others may copy, modify, distribute, or perform your work for any purpose without attribution.
004748---
49···5354**Type:** `token`
5556+Attribution required. Others may copy, distribute, remix, and build upon your work, even commercially, if they credit you.
05758---
59···6364**Type:** `token`
6566+Attribution + share-alike. Others may adapt and build upon your work, even commercially, if they credit you and license their new creations under identical terms.
006768---
69···7374**Type:** `token`
7576+Attribution + non-commercial. Others may adapt and build upon your work for non-commercial purposes only, and must credit you.
07778---
79···8384**Type:** `token`
8586+Attribution + non-commercial + share-alike. Others may adapt and build upon your work for non-commercial purposes only, must credit you, and must license their new creations under identical terms.
008788---
89···9394**Type:** `token`
9596+Attribution + no derivatives. Others may reuse your work, even commercially, but it must remain unchanged and you must be credited.
09798---
99···103104**Type:** `token`
105106+Attribution + non-commercial + no derivatives. Others may download and share your work with credit, but cannot change it or use it commercially.
0107108---
109
···2930**Type:** `token`
3132-The content could be perceived as offensive due to the discussion or display of
33-death.
3435---
36···4041**Type:** `token`
4243-The content contains a portrayal of the use or abuse of mind altering
44-substances.
4546---
47···5152**Type:** `token`
5354-The content contains violent actions of a fantasy nature, involving human or
55-non-human characters in situations easily distinguishable from real life.
5657---
58···6263**Type:** `token`
6465-The content contains flashing lights that could be harmful to viewers with
66-seizure disorders such as photosensitive epilepsy.
6768---
69···9394**Type:** `token`
9596-The content contains information that can be used to identify a particular
97-individual, such as a name, phone number, email address, physical address, or IP
98-address.
99100---
101···105106**Type:** `token`
107108-The content could be perceived as offensive due to the discussion or display of
109-sexuality.
110111---
112···116117**Type:** `token`
118119-The content could be perceived as distressing due to the discussion or display
120-of suffering or triggering topics, including suicide, eating disorders or self
121-harm.
122123---
124···128129**Type:** `token`
130131-The content could be perceived as offensive due to the discussion or display of
132-violence.
133134---
135
···2930**Type:** `token`
3132+The content could be perceived as offensive due to the discussion or display of death.
03334---
35···3940**Type:** `token`
4142+The content contains a portrayal of the use or abuse of mind altering substances.
04344---
45···4950**Type:** `token`
5152+The content contains violent actions of a fantasy nature, involving human or non-human characters in situations easily distinguishable from real life.
05354---
55···5960**Type:** `token`
6162+The content contains flashing lights that could be harmful to viewers with seizure disorders such as photosensitive epilepsy.
06364---
65···8990**Type:** `token`
9192+The content contains information that can be used to identify a particular individual, such as a name, phone number, email address, physical address, or IP address.
009394---
95···99100**Type:** `token`
101102+The content could be perceived as offensive due to the discussion or display of sexuality.
0103104---
105···109110**Type:** `token`
111112+The content could be perceived as distressing due to the discussion or display of suffering or triggering topics, including suicide, eating disorders or self harm.
00113114---
115···119120**Type:** `token`
121122+The content could be perceived as offensive due to the discussion or display of violence.
0123124---
125
···1314**Type:** `procedure`
1516-Create a block (ban) on behalf of a streamer. Requires 'ban' permission. Creates
17-an app.bsky.graph.block record in the streamer's repository.
1819**Parameters:** _(None defined)_
20···46**Possible Errors:**
4748- `Unauthorized`: The request lacks valid authentication credentials.
49-- `Forbidden`: The caller does not have permission to create blocks for this
50- streamer.
51-- `SessionNotFound`: The streamer's OAuth session could not be found or is
52- invalid.
5354---
55
···1314**Type:** `procedure`
1516+Create a block (ban) on behalf of a streamer. Requires 'ban' permission. Creates an app.bsky.graph.block record in the streamer's repository.
01718**Parameters:** _(None defined)_
19···45**Possible Errors:**
4647- `Unauthorized`: The request lacks valid authentication credentials.
48+- `Forbidden`: The caller does not have permission to create blocks for this streamer.
49+- `SessionNotFound`: The streamer's OAuth session could not be found or is invalid.
005051---
52
···1314**Type:** `procedure`
1516-Create a gate (hide message) on behalf of a streamer. Requires 'hide'
17-permission. Creates a place.stream.chat.gate record in the streamer's
18-repository.
1920**Parameters:** _(None defined)_
21···46**Possible Errors:**
4748- `Unauthorized`: The request lacks valid authentication credentials.
49-- `Forbidden`: The caller does not have permission to hide messages for this
50- streamer.
51-- `SessionNotFound`: The streamer's OAuth session could not be found or is
52- invalid.
5354---
55
···1314**Type:** `procedure`
1516+Create a gate (hide message) on behalf of a streamer. Requires 'hide' permission. Creates a place.stream.chat.gate record in the streamer's repository.
001718**Parameters:** _(None defined)_
19···44**Possible Errors:**
4546- `Unauthorized`: The request lacks valid authentication credentials.
47+- `Forbidden`: The caller does not have permission to hide messages for this streamer.
48+- `SessionNotFound`: The streamer's OAuth session could not be found or is invalid.
004950---
51
···1314**Type:** `procedure`
1516-Delete a block (unban) on behalf of a streamer. Requires 'ban' permission.
17-Deletes an app.bsky.graph.block record from the streamer's repository.
1819**Parameters:** _(None defined)_
20···3738**Schema Type:** `object`
3940-_(No properties defined)_ **Possible Errors:**
04142- `Unauthorized`: The request lacks valid authentication credentials.
43-- `Forbidden`: The caller does not have permission to delete blocks for this
44- streamer.
45-- `SessionNotFound`: The streamer's OAuth session could not be found or is
46- invalid.
4748---
49
···1314**Type:** `procedure`
1516+Delete a block (unban) on behalf of a streamer. Requires 'ban' permission. Deletes an app.bsky.graph.block record from the streamer's repository.
01718**Parameters:** _(None defined)_
19···3637**Schema Type:** `object`
3839+_(No properties defined)_
40+**Possible Errors:**
4142- `Unauthorized`: The request lacks valid authentication credentials.
43+- `Forbidden`: The caller does not have permission to delete blocks for this streamer.
44+- `SessionNotFound`: The streamer's OAuth session could not be found or is invalid.
004546---
47
···1314**Type:** `procedure`
1516-Delete a gate (unhide message) on behalf of a streamer. Requires 'hide'
17-permission. Deletes a place.stream.chat.gate record from the streamer's
18-repository.
1920**Parameters:** _(None defined)_
21···3839**Schema Type:** `object`
4041-_(No properties defined)_ **Possible Errors:**
04243- `Unauthorized`: The request lacks valid authentication credentials.
44-- `Forbidden`: The caller does not have permission to unhide messages for this
45- streamer.
46-- `SessionNotFound`: The streamer's OAuth session could not be found or is
47- invalid.
4849---
50
···1314**Type:** `procedure`
1516+Delete a gate (unhide message) on behalf of a streamer. Requires 'hide' permission. Deletes a place.stream.chat.gate record from the streamer's repository.
001718**Parameters:** _(None defined)_
19···3637**Schema Type:** `object`
3839+_(No properties defined)_
40+**Possible Errors:**
4142- `Unauthorized`: The request lacks valid authentication credentials.
43+- `Forbidden`: The caller does not have permission to unhide messages for this streamer.
44+- `SessionNotFound`: The streamer's OAuth session could not be found or is invalid.
004546---
47
···1314**Type:** `procedure`
1516-Update livestream metadata on behalf of a streamer. Requires 'livestream.manage'
17-permission. Updates a place.stream.livestream record in the streamer's
18-repository.
1920**Parameters:** _(None defined)_
21···47**Possible Errors:**
4849- `Unauthorized`: The request lacks valid authentication credentials.
50-- `Forbidden`: The caller does not have permission to update livestream metadata
51- for this streamer.
52-- `SessionNotFound`: The streamer's OAuth session could not be found or is
53- invalid.
54- `RecordNotFound`: The specified livestream record does not exist.
5556---
···1314**Type:** `procedure`
1516+Update livestream metadata on behalf of a streamer. Requires 'livestream.manage' permission. Updates a place.stream.livestream record in the streamer's repository.
001718**Parameters:** _(None defined)_
19···45**Possible Errors:**
4647- `Unauthorized`: The request lacks valid authentication credentials.
48+- `Forbidden`: The caller does not have permission to update livestream metadata for this streamer.
49+- `SessionNotFound`: The streamer's OAuth session could not be found or is invalid.
0050- `RecordNotFound`: The specified livestream record does not exist.
5152---
···12 lexutil "github.com/bluesky-social/indigo/lex/util"
13)
14000000000000000015// ChatDefs_MessageView is a "messageView" in the place.stream.chat.defs schema.
16type ChatDefs_MessageView struct {
17 LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.chat.defs#messageView"`
···21 // deleted: If true, this message has been deleted or labeled and should be cleared from the cache
22 Deleted *bool `json:"deleted,omitempty" cborgen:"deleted,omitempty"`
23 IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`
24- Record *lexutil.LexiconTypeDecoder `json:"record" cborgen:"record"`
25 ReplyTo *ChatDefs_MessageView_ReplyTo `json:"replyTo,omitempty" cborgen:"replyTo,omitempty"`
26 Uri string `json:"uri" cborgen:"uri"`
00000000000000000000000000027}
2829type ChatDefs_MessageView_ReplyTo struct {
···12 lexutil "github.com/bluesky-social/indigo/lex/util"
13)
1415+// ChatDefs_MessageRecordView is a "messageRecordView" in the place.stream.chat.defs schema.
16+//
17+// The content of a chat message.
18+type ChatDefs_MessageRecordView struct {
19+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.chat.defs#messageRecordView"`
20+ // createdAt: Client-declared timestamp when this message was originally created.
21+ CreatedAt string `json:"createdAt" cborgen:"createdAt"`
22+ // facets: Annotations of text (mentions, URLs, etc)
23+ Facets []*RichtextDefs_FacetView `json:"facets,omitempty" cborgen:"facets,omitempty"`
24+ Reply *ChatMessage_ReplyRef `json:"reply,omitempty" cborgen:"reply,omitempty"`
25+ // streamer: The DID of the streamer whose chat this is.
26+ Streamer string `json:"streamer" cborgen:"streamer"`
27+ // text: The primary message content. May be an empty string, if there are embeds.
28+ Text string `json:"text" cborgen:"text"`
29+}
30+31// ChatDefs_MessageView is a "messageView" in the place.stream.chat.defs schema.
32type ChatDefs_MessageView struct {
33 LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.chat.defs#messageView"`
···37 // deleted: If true, this message has been deleted or labeled and should be cleared from the cache
38 Deleted *bool `json:"deleted,omitempty" cborgen:"deleted,omitempty"`
39 IndexedAt string `json:"indexedAt" cborgen:"indexedAt"`
40+ Record *ChatDefs_MessageView_Record `json:"record" cborgen:"record"`
41 ReplyTo *ChatDefs_MessageView_ReplyTo `json:"replyTo,omitempty" cborgen:"replyTo,omitempty"`
42 Uri string `json:"uri" cborgen:"uri"`
43+}
44+45+type ChatDefs_MessageView_Record struct {
46+ ChatDefs_MessageRecordView *ChatDefs_MessageRecordView
47+}
48+49+func (t *ChatDefs_MessageView_Record) MarshalJSON() ([]byte, error) {
50+ if t.ChatDefs_MessageRecordView != nil {
51+ t.ChatDefs_MessageRecordView.LexiconTypeID = "place.stream.chat.defs#messageRecordView"
52+ return json.Marshal(t.ChatDefs_MessageRecordView)
53+ }
54+ return nil, fmt.Errorf("can not marshal empty union as JSON")
55+}
56+57+func (t *ChatDefs_MessageView_Record) UnmarshalJSON(b []byte) error {
58+ typ, err := lexutil.TypeExtract(b)
59+ if err != nil {
60+ return err
61+ }
62+63+ switch typ {
64+ case "place.stream.chat.defs#messageRecordView":
65+ t.ChatDefs_MessageRecordView = new(ChatDefs_MessageRecordView)
66+ return json.Unmarshal(b, t.ChatDefs_MessageRecordView)
67+ default:
68+ return nil
69+ }
70}
7172type ChatDefs_MessageView_ReplyTo struct {