···3737export { default as VideoRetry } from "./components/mobile-player/video-retry";
3838export * from "./lib/system-messages";
39394040+export * from "./components/stream-notification";
4141+export * from "./lib/stream-notifications";
4242+4043export * from "./utils/format-handle";
41444245export { DanmuOverlay } from "./components/danmu/danmu-overlay";
···11+---
22+title: place.stream.live.teleport
33+description: Reference for the place.stream.live.teleport lexicon
44+---
55+66+**Lexicon Version:** 1
77+88+## Definitions
99+1010+<a name="main"></a>
1111+1212+### `main`
1313+1414+**Type:** `record`
1515+1616+Record defining a 'teleport', that is active during a certain time.
1717+1818+**Record Key:** `tid`
1919+2020+**Record Properties:**
2121+2222+| Name | Type | Req'd | Description | Constraints |
2323+| ----------------- | --------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
2424+| `streamer` | `string` | ✅ | The DID of the streamer to teleport to. | Format: `did` |
2525+| `startsAt` | `string` | ✅ | The time the teleport becomes active. | Format: `datetime` |
2626+| `durationSeconds` | `integer` | ❌ | The time limit in seconds for the teleport. If not set, the teleport is permanent. Must be at least 60 seconds, and no more than 32,400 seconds (9 hours). | Min: 60<br/>Max: 32400 |
2727+2828+---
2929+3030+## Lexicon Source
3131+3232+```json
3333+{
3434+ "lexicon": 1,
3535+ "id": "place.stream.live.teleport",
3636+ "defs": {
3737+ "main": {
3838+ "type": "record",
3939+ "key": "tid",
4040+ "description": "Record defining a 'teleport', that is active during a certain time.",
4141+ "record": {
4242+ "type": "object",
4343+ "required": ["streamer", "startsAt"],
4444+ "properties": {
4545+ "streamer": {
4646+ "type": "string",
4747+ "format": "did",
4848+ "description": "The DID of the streamer to teleport to."
4949+ },
5050+ "startsAt": {
5151+ "type": "string",
5252+ "format": "datetime",
5353+ "description": "The time the teleport becomes active."
5454+ },
5555+ "durationSeconds": {
5656+ "type": "integer",
5757+ "description": "The time limit in seconds for the teleport. If not set, the teleport is permanent. Must be at least 60 seconds, and no more than 32,400 seconds (9 hours).",
5858+ "minimum": 60,
5959+ "maximum": 32400
6060+ }
6161+ }
6262+ }
6363+ }
6464+ }
6565+}
6666+```
···11+{
22+ "lexicon": 1,
33+ "id": "place.stream.live.denyTeleport",
44+ "defs": {
55+ "main": {
66+ "type": "procedure",
77+ "description": "Deny an incoming teleport request.",
88+ "input": {
99+ "encoding": "application/json",
1010+ "schema": {
1111+ "type": "object",
1212+ "required": ["uri"],
1313+ "properties": {
1414+ "uri": {
1515+ "type": "string",
1616+ "format": "at-uri",
1717+ "description": "The URI of the teleport record to deny."
1818+ }
1919+ }
2020+ }
2121+ },
2222+ "output": {
2323+ "encoding": "application/json",
2424+ "schema": {
2525+ "type": "object",
2626+ "required": ["success"],
2727+ "properties": {
2828+ "success": {
2929+ "type": "boolean",
3030+ "description": "Whether the teleport was successfully denied."
3131+ }
3232+ }
3333+ }
3434+ },
3535+ "errors": [
3636+ {
3737+ "name": "TeleportNotFound",
3838+ "description": "The specified teleport was not found."
3939+ },
4040+ {
4141+ "name": "Unauthorized",
4242+ "description": "The authenticated user is not the target of this teleport."
4343+ }
4444+ ]
4545+ }
4646+ }
4747+}
+33
lexicons/place/stream/live/teleport.json
···11+{
22+ "lexicon": 1,
33+ "id": "place.stream.live.teleport",
44+ "defs": {
55+ "main": {
66+ "type": "record",
77+ "key": "tid",
88+ "description": "Record defining a 'teleport', that is active during a certain time.",
99+ "record": {
1010+ "type": "object",
1111+ "required": ["streamer", "startsAt"],
1212+ "properties": {
1313+ "streamer": {
1414+ "type": "string",
1515+ "format": "did",
1616+ "description": "The DID of the streamer to teleport to."
1717+ },
1818+ "startsAt": {
1919+ "type": "string",
2020+ "format": "datetime",
2121+ "description": "The time the teleport becomes active."
2222+ },
2323+ "durationSeconds": {
2424+ "type": "integer",
2525+ "description": "The time limit in seconds for the teleport. If not set, the teleport is permanent. Must be at least 60 seconds, and no more than 32,400 seconds (9 hours).",
2626+ "minimum": 60,
2727+ "maximum": 32400
2828+ }
2929+ }
3030+ }
3131+ }
3232+ }
3333+}
+48
lexicons/place/stream/livestream.json
···8888 "count": { "type": "integer" }
8989 }
9090 },
9191+ "teleportArrival": {
9292+ "type": "object",
9393+ "required": ["teleportUri", "source", "viewerCount", "startsAt"],
9494+ "properties": {
9595+ "teleportUri": {
9696+ "type": "string",
9797+ "format": "at-uri",
9898+ "description": "The URI of the teleport record"
9999+ },
100100+ "source": {
101101+ "type": "ref",
102102+ "ref": "app.bsky.actor.defs#profileViewBasic",
103103+ "description": "The streamer who is teleporting their viewers here"
104104+ },
105105+ "chatProfile": {
106106+ "type": "ref",
107107+ "ref": "place.stream.chat.profile",
108108+ "description": "The chat profile of the source streamer"
109109+ },
110110+ "viewerCount": {
111111+ "type": "integer",
112112+ "description": "How many viewers are arriving from this teleport"
113113+ },
114114+ "startsAt": {
115115+ "type": "string",
116116+ "format": "datetime",
117117+ "description": "When this teleport started"
118118+ }
119119+ }
120120+ },
121121+ "teleportCanceled": {
122122+ "type": "object",
123123+ "required": ["teleportUri", "reason"],
124124+ "properties": {
125125+ "teleportUri": {
126126+ "type": "string",
127127+ "format": "at-uri",
128128+ "description": "The URI of the teleport record that was canceled"
129129+ },
130130+ "reason": {
131131+ "type": "string",
132132+ "enum": ["deleted", "denied", "expired"],
133133+ "description": "Why this teleport was canceled"
134134+ }
135135+ }
136136+ },
91137 "streamplaceAnything": {
92138 "type": "object",
93139 "required": ["livestream"],
···97143 "refs": [
98144 "#livestreamView",
99145 "#viewerCount",
146146+ "#teleportArrival",
147147+ "#teleportCanceled",
100148 "place.stream.defs#blockView",
101149 "place.stream.defs#renditions",
102150 "place.stream.defs#rendition",
+38
pkg/api/websocket.go
···77 "net/http"
88 "time"
991010+ bsky "github.com/bluesky-social/indigo/api/bsky"
1011 "github.com/google/uuid"
1112 "github.com/gorilla/websocket"
1213 "github.com/julienschmidt/httprouter"
···238239 }
239240 for _, message := range messages {
240241 initialBurst <- message
242242+ }
243243+ }()
244244+245245+ go func() {
246246+ teleports, err := a.Model.GetActiveTeleportsToRepo(repoDID)
247247+ if err != nil {
248248+ log.Error(ctx, "could not get active teleports", "error", err)
249249+ return
250250+ }
251251+ // just send the latest one if it started <3m ago
252252+ if len(teleports) > 0 && teleports[0].StartsAt.After(time.Now().Add(-3*time.Minute)) {
253253+ tp := teleports[0]
254254+ if tp.Repo == nil {
255255+ log.Error(ctx, "teleportee repo is nil", "uri", tp.URI)
256256+ }
257257+ viewerCount := a.Bus.GetViewerCount(tp.RepoDID)
258258+ arrivalMsg := streamplace.Livestream_TeleportArrival{
259259+ LexiconTypeID: "place.stream.livestream#teleportArrival",
260260+ TeleportUri: tp.URI,
261261+ Source: &bsky.ActorDefs_ProfileViewBasic{
262262+ Did: tp.RepoDID,
263263+ Handle: tp.Repo.Handle,
264264+ },
265265+ ViewerCount: int64(viewerCount),
266266+ StartsAt: tp.StartsAt.Format(time.RFC3339),
267267+ }
268268+269269+ // get the source chat profile
270270+ chatProfile, err := a.Model.GetChatProfile(ctx, tp.RepoDID)
271271+ if err == nil && chatProfile != nil {
272272+ spcp, err := chatProfile.ToStreamplaceChatProfile()
273273+ if err == nil {
274274+ arrivalMsg.ChatProfile = spcp
275275+ }
276276+ }
277277+278278+ initialBurst <- arrivalMsg
241279 }
242280 }()
243281
···5526552655275527 return nil
55285528}
55295529+func (t *LiveTeleport) MarshalCBOR(w io.Writer) error {
55305530+ if t == nil {
55315531+ _, err := w.Write(cbg.CborNull)
55325532+ return err
55335533+ }
55345534+55355535+ cw := cbg.NewCborWriter(w)
55365536+ fieldCount := 4
55375537+55385538+ if t.DurationSeconds == nil {
55395539+ fieldCount--
55405540+ }
55415541+55425542+ if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
55435543+ return err
55445544+ }
55455545+55465546+ // t.LexiconTypeID (string) (string)
55475547+ if len("$type") > 1000000 {
55485548+ return xerrors.Errorf("Value in field \"$type\" was too long")
55495549+ }
55505550+55515551+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
55525552+ return err
55535553+ }
55545554+ if _, err := cw.WriteString(string("$type")); err != nil {
55555555+ return err
55565556+ }
55575557+55585558+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("place.stream.live.teleport"))); err != nil {
55595559+ return err
55605560+ }
55615561+ if _, err := cw.WriteString(string("place.stream.live.teleport")); err != nil {
55625562+ return err
55635563+ }
55645564+55655565+ // t.StartsAt (string) (string)
55665566+ if len("startsAt") > 1000000 {
55675567+ return xerrors.Errorf("Value in field \"startsAt\" was too long")
55685568+ }
55695569+55705570+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("startsAt"))); err != nil {
55715571+ return err
55725572+ }
55735573+ if _, err := cw.WriteString(string("startsAt")); err != nil {
55745574+ return err
55755575+ }
55765576+55775577+ if len(t.StartsAt) > 1000000 {
55785578+ return xerrors.Errorf("Value in field t.StartsAt was too long")
55795579+ }
55805580+55815581+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.StartsAt))); err != nil {
55825582+ return err
55835583+ }
55845584+ if _, err := cw.WriteString(string(t.StartsAt)); err != nil {
55855585+ return err
55865586+ }
55875587+55885588+ // t.Streamer (string) (string)
55895589+ if len("streamer") > 1000000 {
55905590+ return xerrors.Errorf("Value in field \"streamer\" was too long")
55915591+ }
55925592+55935593+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("streamer"))); err != nil {
55945594+ return err
55955595+ }
55965596+ if _, err := cw.WriteString(string("streamer")); err != nil {
55975597+ return err
55985598+ }
55995599+56005600+ if len(t.Streamer) > 1000000 {
56015601+ return xerrors.Errorf("Value in field t.Streamer was too long")
56025602+ }
56035603+56045604+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Streamer))); err != nil {
56055605+ return err
56065606+ }
56075607+ if _, err := cw.WriteString(string(t.Streamer)); err != nil {
56085608+ return err
56095609+ }
56105610+56115611+ // t.DurationSeconds (int64) (int64)
56125612+ if t.DurationSeconds != nil {
56135613+56145614+ if len("durationSeconds") > 1000000 {
56155615+ return xerrors.Errorf("Value in field \"durationSeconds\" was too long")
56165616+ }
56175617+56185618+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("durationSeconds"))); err != nil {
56195619+ return err
56205620+ }
56215621+ if _, err := cw.WriteString(string("durationSeconds")); err != nil {
56225622+ return err
56235623+ }
56245624+56255625+ if t.DurationSeconds == nil {
56265626+ if _, err := cw.Write(cbg.CborNull); err != nil {
56275627+ return err
56285628+ }
56295629+ } else {
56305630+ if *t.DurationSeconds >= 0 {
56315631+ if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.DurationSeconds)); err != nil {
56325632+ return err
56335633+ }
56345634+ } else {
56355635+ if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-*t.DurationSeconds-1)); err != nil {
56365636+ return err
56375637+ }
56385638+ }
56395639+ }
56405640+56415641+ }
56425642+ return nil
56435643+}
56445644+56455645+func (t *LiveTeleport) UnmarshalCBOR(r io.Reader) (err error) {
56465646+ *t = LiveTeleport{}
56475647+56485648+ cr := cbg.NewCborReader(r)
56495649+56505650+ maj, extra, err := cr.ReadHeader()
56515651+ if err != nil {
56525652+ return err
56535653+ }
56545654+ defer func() {
56555655+ if err == io.EOF {
56565656+ err = io.ErrUnexpectedEOF
56575657+ }
56585658+ }()
56595659+56605660+ if maj != cbg.MajMap {
56615661+ return fmt.Errorf("cbor input should be of type map")
56625662+ }
56635663+56645664+ if extra > cbg.MaxLength {
56655665+ return fmt.Errorf("LiveTeleport: map struct too large (%d)", extra)
56665666+ }
56675667+56685668+ n := extra
56695669+56705670+ nameBuf := make([]byte, 15)
56715671+ for i := uint64(0); i < n; i++ {
56725672+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
56735673+ if err != nil {
56745674+ return err
56755675+ }
56765676+56775677+ if !ok {
56785678+ // Field doesn't exist on this type, so ignore it
56795679+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
56805680+ return err
56815681+ }
56825682+ continue
56835683+ }
56845684+56855685+ switch string(nameBuf[:nameLen]) {
56865686+ // t.LexiconTypeID (string) (string)
56875687+ case "$type":
56885688+56895689+ {
56905690+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
56915691+ if err != nil {
56925692+ return err
56935693+ }
56945694+56955695+ t.LexiconTypeID = string(sval)
56965696+ }
56975697+ // t.StartsAt (string) (string)
56985698+ case "startsAt":
56995699+57005700+ {
57015701+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
57025702+ if err != nil {
57035703+ return err
57045704+ }
57055705+57065706+ t.StartsAt = string(sval)
57075707+ }
57085708+ // t.Streamer (string) (string)
57095709+ case "streamer":
57105710+57115711+ {
57125712+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
57135713+ if err != nil {
57145714+ return err
57155715+ }
57165716+57175717+ t.Streamer = string(sval)
57185718+ }
57195719+ // t.DurationSeconds (int64) (int64)
57205720+ case "durationSeconds":
57215721+ {
57225722+57235723+ b, err := cr.ReadByte()
57245724+ if err != nil {
57255725+ return err
57265726+ }
57275727+ if b != cbg.CborNull[0] {
57285728+ if err := cr.UnreadByte(); err != nil {
57295729+ return err
57305730+ }
57315731+ maj, extra, err := cr.ReadHeader()
57325732+ if err != nil {
57335733+ return err
57345734+ }
57355735+ var extraI int64
57365736+ switch maj {
57375737+ case cbg.MajUnsignedInt:
57385738+ extraI = int64(extra)
57395739+ if extraI < 0 {
57405740+ return fmt.Errorf("int64 positive overflow")
57415741+ }
57425742+ case cbg.MajNegativeInt:
57435743+ extraI = int64(extra)
57445744+ if extraI < 0 {
57455745+ return fmt.Errorf("int64 negative overflow")
57465746+ }
57475747+ extraI = -1 - extraI
57485748+ default:
57495749+ return fmt.Errorf("wrong type for int64 field: %d", maj)
57505750+ }
57515751+57525752+ t.DurationSeconds = (*int64)(&extraI)
57535753+ }
57545754+ }
57555755+57565756+ default:
57575757+ // Field doesn't exist on this type, so ignore it
57585758+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
57595759+ return err
57605760+ }
57615761+ }
57625762+ }
57635763+57645764+ return nil
57655765+}
55295766func (t *LiveRecommendations) MarshalCBOR(w io.Writer) error {
55305767 if t == nil {
55315768 _, err := w.Write(cbg.CborNull)
+33
pkg/streamplace/livedenyTeleport.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.live.denyTeleport
44+55+package streamplace
66+77+import (
88+ "context"
99+1010+ lexutil "github.com/bluesky-social/indigo/lex/util"
1111+)
1212+1313+// LiveDenyTeleport_Input is the input argument to a place.stream.live.denyTeleport call.
1414+type LiveDenyTeleport_Input struct {
1515+ // uri: The URI of the teleport record to deny.
1616+ Uri string `json:"uri" cborgen:"uri"`
1717+}
1818+1919+// LiveDenyTeleport_Output is the output of a place.stream.live.denyTeleport call.
2020+type LiveDenyTeleport_Output struct {
2121+ // success: Whether the teleport was successfully denied.
2222+ Success bool `json:"success" cborgen:"success"`
2323+}
2424+2525+// LiveDenyTeleport calls the XRPC method "place.stream.live.denyTeleport".
2626+func LiveDenyTeleport(ctx context.Context, c lexutil.LexClient, input *LiveDenyTeleport_Input) (*LiveDenyTeleport_Output, error) {
2727+ var out LiveDenyTeleport_Output
2828+ if err := c.LexDo(ctx, lexutil.Procedure, "application/json", "place.stream.live.denyTeleport", nil, input, &out); err != nil {
2929+ return nil, err
3030+ }
3131+3232+ return &out, nil
3333+}
+23
pkg/streamplace/liveteleport.go
···11+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
22+33+// Lexicon schema: place.stream.live.teleport
44+55+package streamplace
66+77+import (
88+ lexutil "github.com/bluesky-social/indigo/lex/util"
99+)
1010+1111+func init() {
1212+ lexutil.RegisterType("place.stream.live.teleport", &LiveTeleport{})
1313+}
1414+1515+type LiveTeleport struct {
1616+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.live.teleport"`
1717+ // durationSeconds: The time limit in seconds for the teleport. If not set, the teleport is permanent. Must be at least 60 seconds, and no more than 32,400 seconds (9 hours).
1818+ DurationSeconds *int64 `json:"durationSeconds,omitempty" cborgen:"durationSeconds,omitempty"`
1919+ // startsAt: The time the teleport becomes active.
2020+ StartsAt string `json:"startsAt" cborgen:"startsAt"`
2121+ // streamer: The DID of the streamer to teleport to.
2222+ Streamer string `json:"streamer" cborgen:"streamer"`
2323+}
+46-6
pkg/streamplace/streamlivestream.go
···5959}
60606161type Livestream_StreamplaceAnything_Livestream struct {
6262- Livestream_LivestreamView *Livestream_LivestreamView
6363- Livestream_ViewerCount *Livestream_ViewerCount
6464- Defs_BlockView *Defs_BlockView
6565- Defs_Renditions *Defs_Renditions
6666- Defs_Rendition *Defs_Rendition
6767- ChatDefs_MessageView *ChatDefs_MessageView
6262+ Livestream_LivestreamView *Livestream_LivestreamView
6363+ Livestream_ViewerCount *Livestream_ViewerCount
6464+ Livestream_TeleportArrival *Livestream_TeleportArrival
6565+ Livestream_TeleportCanceled *Livestream_TeleportCanceled
6666+ Defs_BlockView *Defs_BlockView
6767+ Defs_Renditions *Defs_Renditions
6868+ Defs_Rendition *Defs_Rendition
6969+ ChatDefs_MessageView *ChatDefs_MessageView
6870}
69717072func (t *Livestream_StreamplaceAnything_Livestream) MarshalJSON() ([]byte, error) {
···7577 if t.Livestream_ViewerCount != nil {
7678 t.Livestream_ViewerCount.LexiconTypeID = "place.stream.livestream#viewerCount"
7779 return json.Marshal(t.Livestream_ViewerCount)
8080+ }
8181+ if t.Livestream_TeleportArrival != nil {
8282+ t.Livestream_TeleportArrival.LexiconTypeID = "place.stream.livestream#teleportArrival"
8383+ return json.Marshal(t.Livestream_TeleportArrival)
8484+ }
8585+ if t.Livestream_TeleportCanceled != nil {
8686+ t.Livestream_TeleportCanceled.LexiconTypeID = "place.stream.livestream#teleportCanceled"
8787+ return json.Marshal(t.Livestream_TeleportCanceled)
7888 }
7989 if t.Defs_BlockView != nil {
8090 t.Defs_BlockView.LexiconTypeID = "place.stream.defs#blockView"
···108118 case "place.stream.livestream#viewerCount":
109119 t.Livestream_ViewerCount = new(Livestream_ViewerCount)
110120 return json.Unmarshal(b, t.Livestream_ViewerCount)
121121+ case "place.stream.livestream#teleportArrival":
122122+ t.Livestream_TeleportArrival = new(Livestream_TeleportArrival)
123123+ return json.Unmarshal(b, t.Livestream_TeleportArrival)
124124+ case "place.stream.livestream#teleportCanceled":
125125+ t.Livestream_TeleportCanceled = new(Livestream_TeleportCanceled)
126126+ return json.Unmarshal(b, t.Livestream_TeleportCanceled)
111127 case "place.stream.defs#blockView":
112128 t.Defs_BlockView = new(Defs_BlockView)
113129 return json.Unmarshal(b, t.Defs_BlockView)
···123139 default:
124140 return nil
125141 }
142142+}
143143+144144+// Livestream_TeleportArrival is a "teleportArrival" in the place.stream.livestream schema.
145145+type Livestream_TeleportArrival struct {
146146+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.livestream#teleportArrival"`
147147+ // chatProfile: The chat profile of the source streamer
148148+ ChatProfile *ChatProfile `json:"chatProfile,omitempty" cborgen:"chatProfile,omitempty"`
149149+ // source: The streamer who is teleporting their viewers here
150150+ Source *appbsky.ActorDefs_ProfileViewBasic `json:"source" cborgen:"source"`
151151+ // startsAt: When this teleport started
152152+ StartsAt string `json:"startsAt" cborgen:"startsAt"`
153153+ // teleportUri: The URI of the teleport record
154154+ TeleportUri string `json:"teleportUri" cborgen:"teleportUri"`
155155+ // viewerCount: How many viewers are arriving from this teleport
156156+ ViewerCount int64 `json:"viewerCount" cborgen:"viewerCount"`
157157+}
158158+159159+// Livestream_TeleportCanceled is a "teleportCanceled" in the place.stream.livestream schema.
160160+type Livestream_TeleportCanceled struct {
161161+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.livestream#teleportCanceled"`
162162+ // reason: Why this teleport was canceled
163163+ Reason string `json:"reason" cborgen:"reason"`
164164+ // teleportUri: The URI of the teleport record that was canceled
165165+ TeleportUri string `json:"teleportUri" cborgen:"teleportUri"`
126166}
127167128168// Livestream_ViewerCount is a "viewerCount" in the place.stream.livestream schema.