From 7d82197645c3dc3c14950ca1d3eb29c708bef6bf Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Tue, 26 May 2026 20:24:58 -0700 Subject: [PATCH] player: sort of kind of working on mobile! og cards! --- go.mod | 8 +-- go.sum | 16 +++--- js/app/components/mobile/ui.tsx | 18 +++++-- .../mobile-player/video-async.native.tsx | 2 +- .../src/hooks/useSegmentDimensions.tsx | 54 +++++++++++++++---- .../media/place-stream-media-getvideo.md | 8 +++ .../media/place-stream-media-track.md | 38 +++++++++++++ .../content/docs/lex-reference/openapi.json | 24 +++++++++ js/streamplace/src/useful-types.ts | 6 +++ lexicons/place/stream/media/getVideo.json | 7 +++ lexicons/place/stream/media/track.json | 13 +++++ pkg/linking/linking.go | 36 ++++++------- pkg/linking/linking_test.go | 5 +- pkg/model/video.go | 24 +++++++++ pkg/streamplace/mediagetVideo.go | 1 + pkg/streamplace/mediatrack.go | 9 ++++ 16 files changed, 220 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 147824dd..d98b3625 100644 --- a/go.mod +++ b/go.mod @@ -63,9 +63,9 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/slok/go-http-metrics v0.13.0 github.com/starttoaster/prometheus-exporter-scraper v0.0.1 - github.com/streamplace/atproto-oauth-golang v0.0.0-20260413212710-98956064d06c - github.com/streamplace/muxl/go v0.0.0-20260528173527-91f80c95ca45 - github.com/streamplace/oatproxy v0.0.0-20260602023256-6c7e423d20a0 + github.com/streamplace/atproto-oauth-golang v0.0.0-20250619231223-a9c04fb888ac + github.com/streamplace/muxl/go v0.2.1 + github.com/streamplace/oatproxy v0.0.0-20260508220721-f8852e8dbf44 github.com/stretchr/testify v1.11.1 github.com/tdewolff/canvas v0.0.0-20250728095813-50d4cb1eee71 github.com/tus/tusd/v2 v2.8.0 @@ -496,7 +496,7 @@ require ( github.com/tdewolff/minify/v2 v2.23.4 // indirect github.com/tdewolff/parse/v2 v2.8.0 // indirect github.com/tetafro/godot v1.5.1 // indirect - github.com/tetratelabs/wazero v1.11.0 // indirect + github.com/tetratelabs/wazero v1.12.0 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect github.com/timonwong/loggercheck v0.11.0 // indirect diff --git a/go.sum b/go.sum index 29464487..05aa64ea 100644 --- a/go.sum +++ b/go.sum @@ -1369,16 +1369,16 @@ github.com/starttoaster/prometheus-exporter-scraper v0.0.1 h1:E3Zl3ho3v3Yt2Yk7i9 github.com/starttoaster/prometheus-exporter-scraper v0.0.1/go.mod h1:qNccVOSo0np2jYi6DolRyUICFcMkhH5k1Gs7CYxav1w= github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= -github.com/streamplace/atproto-oauth-golang v0.0.0-20260413212710-98956064d06c h1:IzEPU2O4iL58Nb7aw+7lB9ttnesEwOVVE5oV9NEXemM= -github.com/streamplace/atproto-oauth-golang v0.0.0-20260413212710-98956064d06c/go.mod h1:9LlKkqciiO5lRfbX0n4Wn5KNY9nvFb4R3by8FdW2TWc= +github.com/streamplace/atproto-oauth-golang v0.0.0-20250619231223-a9c04fb888ac h1:heVM4CGox3kfJclSmagsI3hvKk7W52EegnFqqs9CSYk= +github.com/streamplace/atproto-oauth-golang v0.0.0-20250619231223-a9c04fb888ac/go.mod h1:9LlKkqciiO5lRfbX0n4Wn5KNY9nvFb4R3by8FdW2TWc= github.com/streamplace/go-dpop v0.0.0-20250510031900-c897158a8ad4 h1:L1fS4HJSaAyNnkwfuZubgfeZy8rkWmA0cMtH5Z0HqNc= github.com/streamplace/go-dpop v0.0.0-20250510031900-c897158a8ad4/go.mod h1:bGUXY9Wd4mnd+XUrOYZr358J2f6z9QO/dLhL1SsiD+0= github.com/streamplace/indigo v0.0.0-20260218231908-939cdaf0c507 h1:e8M3qPLr37NxEjlr18TaAwGP+OVyherVjgUG5VVmgWI= github.com/streamplace/indigo v0.0.0-20260218231908-939cdaf0c507/go.mod h1:Pm2I1+iDXn/hLbF7XCg/DsZi6uDCiOo7hZGWprSM7k0= -github.com/streamplace/muxl/go v0.0.0-20260528173527-91f80c95ca45 h1:v9KQ+tQnKwbSvS5orbszrd49ucySYi4F9npxoU2sLFU= -github.com/streamplace/muxl/go v0.0.0-20260528173527-91f80c95ca45/go.mod h1:aCyYTW3o6c1Kush9UJ/Yv6EYMUbj8l8GTD7cHKcSxw8= -github.com/streamplace/oatproxy v0.0.0-20260602023256-6c7e423d20a0 h1:FtJV63T621Dg5rQh049uTCx6kGQiiAXCReICz3hJSRQ= -github.com/streamplace/oatproxy v0.0.0-20260602023256-6c7e423d20a0/go.mod h1:wpY+T/wE00jrUhgh2dKXbbE91D36u86KGlENK/hWFkE= +github.com/streamplace/muxl/go v0.2.1 h1:ITjmjP3OREBgLowafspw7R5kGjmKX3uE6/ATAUIAHPk= +github.com/streamplace/muxl/go v0.2.1/go.mod h1:aCyYTW3o6c1Kush9UJ/Yv6EYMUbj8l8GTD7cHKcSxw8= +github.com/streamplace/oatproxy v0.0.0-20260508220721-f8852e8dbf44 h1:b38ToXNQCvKqBlx0SeQYUOhx6uqqnrF5AL6B0Z3zzdk= +github.com/streamplace/oatproxy v0.0.0-20260508220721-f8852e8dbf44/go.mod h1:j1+zdhe1IC0+PTE2rIXk2LqYn4Lz2x9SJaV3eHkQyfs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -1424,8 +1424,8 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.1 h1:PZnjCol4+FqaEzvZg5+O8IY2P3hfY9JzRBNPv1pEDS4= github.com/tetafro/godot v1.5.1/go.mod h1:cCdPtEndkmqqrhiCfkmxDodMQJ/f3L1BCNskCUZdTwk= -github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= -github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= +github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= +github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= diff --git a/js/app/components/mobile/ui.tsx b/js/app/components/mobile/ui.tsx index a9c25be0..2c40879c 100644 --- a/js/app/components/mobile/ui.tsx +++ b/js/app/components/mobile/ui.tsx @@ -160,7 +160,18 @@ export function MobileUi({ const hover = Gesture.Hover().onChange(onPlayerHover); const pan = Gesture.Pan().onChange(onPlayerHover); const tap = Gesture.Tap().onEnd(onPlayerHover); - + let chatSection: React.ReactNode = null; + if ( + mode !== "vod" && + !hideMobileChat && + !isSelfAndNotLive && + playerIsReady && + isPortrait + ) { + chatSection = ( + + ); + } const combined = Gesture.Race(hover, pan, tap); return ( <> @@ -301,10 +312,7 @@ export function MobileUi({ - {hideMobileChat || - (isPortrait && !isSelfAndNotLive && playerIsReady && ( - - ))} + {chatSection} ); } diff --git a/js/components/src/components/mobile-player/video-async.native.tsx b/js/components/src/components/mobile-player/video-async.native.tsx index 8ad93783..da0700f6 100644 --- a/js/components/src/components/mobile-player/video-async.native.tsx +++ b/js/components/src/components/mobile-player/video-async.native.tsx @@ -223,7 +223,7 @@ export function NativeVideo(props?: { { setFullscreen(true); }} diff --git a/js/components/src/hooks/useSegmentDimensions.tsx b/js/components/src/hooks/useSegmentDimensions.tsx index bb95718e..f61b6835 100644 --- a/js/components/src/hooks/useSegmentDimensions.tsx +++ b/js/components/src/hooks/useSegmentDimensions.tsx @@ -1,18 +1,52 @@ -import { useLivestreamStore } from "../livestream-store"; +import { + PlaceStreamMediaTrack, + PlaceStreamSegment, + VideoViewHydrated, +} from "streamplace"; +import { useLivestreamStoreOptional } from "../livestream-store"; +import { useVideoStoreOptional } from "../video-store"; export function useSegmentDimensions() { - const latestSegment = useLivestreamStore((x) => x.segment); + const video = useVideoStoreOptional((x) => x.video); + const latestSegment = useLivestreamStoreOptional((x) => x.segment); - let seg = latestSegment?.video && latestSegment.video[0]; + let height = 0; + let width = 0; + if (video) { + const meta = getVideoMetadata(video); + if (meta) { + height = meta.height || 0; + width = meta.width || 0; + } + } else { + let seg = latestSegment?.video && latestSegment.video[0]; - let ratio = { - height: seg?.height || 0, - width: seg?.width || 0, - }; + height = seg?.height || 0; + width = seg?.width || 0; + } return { - isPlayerRatioGreater: ratio.width > ratio.height, - height: ratio.height, - width: ratio.width, + isPlayerRatioGreater: width > height, + height: height, + width: width, }; } + +function getVideoMetadata( + view: VideoViewHydrated, +): PlaceStreamSegment.Video | null { + if (!view.tracks) { + return null; + } + for (const track of view.tracks) { + const meta = track.record.metadata; + if (!PlaceStreamMediaTrack.isCommonMetadata(meta)) { + continue; + } + if (!meta.video) { + continue; + } + return meta.video; + } + return null; +} diff --git a/js/docs/src/content/docs/lex-reference/media/place-stream-media-getvideo.md b/js/docs/src/content/docs/lex-reference/media/place-stream-media-getvideo.md index ee6c54c8..ecbd9f61 100644 --- a/js/docs/src/content/docs/lex-reference/media/place-stream-media-getvideo.md +++ b/js/docs/src/content/docs/lex-reference/media/place-stream-media-getvideo.md @@ -49,6 +49,7 @@ Get a hydrated view of a place.stream.video record — the record itself plus au | `author` | [`app.bsky.actor.defs#profileViewBasic`](https://github.com/bluesky-social/atproto/tree/main/lexicons/app/bsky/actor/defs.json#profileViewBasic) | ✅ | | | | `record` | `unknown` | ✅ | | | | `viewCounts` | [`#viewCountSummary`](#viewcountsummary) | ✅ | Aggregated view counts across every indexed reporter. Always present; zero-valued (count=0, reporters=0) when no place.stream.media.viewCount records have been observed yet, so consumers can render a count unconditionally. | | +| `tracks` | Array of [`place.stream.media.track#trackView`](/lex-reference/place-stream-media-track#trackview) | ❌ | | | --- @@ -129,6 +130,13 @@ Sums across every place.stream.media.viewCount record indexed for this video, re "type": "ref", "ref": "#viewCountSummary", "description": "Aggregated view counts across every indexed reporter. Always present; zero-valued (count=0, reporters=0) when no place.stream.media.viewCount records have been observed yet, so consumers can render a count unconditionally." + }, + "tracks": { + "type": "array", + "items": { + "type": "ref", + "ref": "place.stream.media.track#trackView" + } } } }, diff --git a/js/docs/src/content/docs/lex-reference/media/place-stream-media-track.md b/js/docs/src/content/docs/lex-reference/media/place-stream-media-track.md index 74f8bafe..b948bd80 100644 --- a/js/docs/src/content/docs/lex-reference/media/place-stream-media-track.md +++ b/js/docs/src/content/docs/lex-reference/media/place-stream-media-track.md @@ -47,6 +47,23 @@ Metadata common to all media types. Contains subobjects for other media types. --- + + +### `trackView` + +**Type:** `object` + +**Properties:** + +| Name | Type | Req'd | Description | Constraints | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | ----------- | ---------------- | +| `uri` | `string` | ✅ | | Format: `at-uri` | +| `cid` | `string` | ✅ | | Format: `cid` | +| `author` | [`app.bsky.actor.defs#profileViewBasic`](https://github.com/bluesky-social/atproto/tree/main/lexicons/app/bsky/actor/defs.json#profileViewBasic) | ✅ | | | +| `record` | `unknown` | ✅ | | | + +--- + ## Lexicon Source ```json @@ -105,6 +122,27 @@ Metadata common to all media types. Contains subobjects for other media types. "ref": "place.stream.segment#audio" } } + }, + "trackView": { + "type": "object", + "required": ["uri", "cid", "author", "record"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "author": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewBasic" + }, + "record": { + "type": "unknown" + } + } } } } diff --git a/js/docs/src/content/docs/lex-reference/openapi.json b/js/docs/src/content/docs/lex-reference/openapi.json index ef53b659..9a5e5efd 100644 --- a/js/docs/src/content/docs/lex-reference/openapi.json +++ b/js/docs/src/content/docs/lex-reference/openapi.json @@ -4732,6 +4732,12 @@ "viewCounts": { "$ref": "#/components/schemas/place.stream.media.getVideo_viewCountSummary", "description": "Aggregated view counts across every indexed reporter. Always present; zero-valued (count=0, reporters=0) when no place.stream.media.viewCount records have been observed yet, so consumers can render a count unconditionally." + }, + "tracks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/place.stream.media.track_trackView" + } } }, "required": ["uri", "cid", "author", "record", "viewCounts"] @@ -5100,6 +5106,24 @@ }, "required": ["count", "bytes", "durationMs", "reporters"] }, + "place.stream.media.track_trackView": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "format": "uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "author": { + "$ref": "#/components/schemas/app.bsky.actor.defs_profileViewBasic" + }, + "record": {} + }, + "required": ["uri", "cid", "author", "record"] + }, "place.stream.video": { "description": "Unknown type" }, diff --git a/js/streamplace/src/useful-types.ts b/js/streamplace/src/useful-types.ts index 230bf200..263d4f8e 100644 --- a/js/streamplace/src/useful-types.ts +++ b/js/streamplace/src/useful-types.ts @@ -4,6 +4,7 @@ import { PlaceStreamChatPinnedRecord, PlaceStreamLivestream, PlaceStreamMediaGetVideo, + PlaceStreamMediaTrack, } from "./lexicons"; export interface LivestreamViewHydrated @@ -23,4 +24,9 @@ export interface PinnedRecordViewHydrated export interface VideoViewHydrated extends PlaceStreamMediaGetVideo.VideoView { record: PlaceStreamLivestream.Record; + tracks: TrackViewHydrated[]; +} + +export interface TrackViewHydrated extends PlaceStreamMediaTrack.TrackView { + record: PlaceStreamMediaTrack.Record; } diff --git a/lexicons/place/stream/media/getVideo.json b/lexicons/place/stream/media/getVideo.json index d210cd52..86f5ae1e 100644 --- a/lexicons/place/stream/media/getVideo.json +++ b/lexicons/place/stream/media/getVideo.json @@ -45,6 +45,13 @@ "type": "ref", "ref": "#viewCountSummary", "description": "Aggregated view counts across every indexed reporter. Always present; zero-valued (count=0, reporters=0) when no place.stream.media.viewCount records have been observed yet, so consumers can render a count unconditionally." + }, + "tracks": { + "type": "array", + "items": { + "type": "ref", + "ref": "place.stream.media.track#trackView" + } } } }, diff --git a/lexicons/place/stream/media/track.json b/lexicons/place/stream/media/track.json index e2ea1fe3..8e0af8ab 100644 --- a/lexicons/place/stream/media/track.json +++ b/lexicons/place/stream/media/track.json @@ -53,6 +53,19 @@ "ref": "place.stream.segment#audio" } } + }, + "trackView": { + "type": "object", + "required": ["uri", "cid", "author", "record"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "author": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewBasic" + }, + "record": { "type": "unknown" } + } } } } diff --git a/pkg/linking/linking.go b/pkg/linking/linking.go index 74899838..aafda0cb 100644 --- a/pkg/linking/linking.go +++ b/pkg/linking/linking.go @@ -214,24 +214,19 @@ func (l *Linker) GenerateVideoCard(ctx context.Context, u *url.URL, vv *streampl imageURL = cardURL.String() } - titleStr := fmt.Sprintf("@%s's video on ", authorHandle) - - // Define all meta tags + // Define all meta tags. The title/description pair is appended after we + // resolve the node's branding title below: the card headline is the + // video's own title, and the author + node go in the description. metaTags := []MetaTag{ - // Basic meta - {Type: "name", Key: "description", Content: video.Title}, - // Facebook Meta Tags {Type: "property", Key: "og:url", Content: outURL}, {Type: "property", Key: "og:type", Content: "video.other"}, - {Type: "property", Key: "og:description", Content: video.Title}, {Type: "property", Key: "og:image", Content: imageURL}, // Twitter Meta Tags {Type: "name", Key: "twitter:card", Content: "summary_large_image"}, {Type: "property", Key: "twitter:domain", Content: u.Host}, {Type: "property", Key: "twitter:url", Content: outURL}, - {Type: "name", Key: "twitter:description", Content: video.Title}, {Type: "name", Key: "twitter:image", Content: imageURL}, } @@ -261,20 +256,21 @@ func (l *Linker) GenerateVideoCard(ctx context.Context, u *url.URL, vv *streampl } } - // do twitter/og title after - metaTags = append(metaTags, MetaTag{ - Type: "property", - Key: "og:title", - Content: fmt.Sprintf("%s%s", titleStr, brandingTitle), - }) - metaTags = append(metaTags, MetaTag{ - Type: "name", - Key: "twitter:title", - Content: fmt.Sprintf("%s%s", titleStr, brandingTitle), - }) + // The card headline is the video's own title; the author + node name go + // in the description. Appended after the branding loop so the description + // can reference the resolved branding title. + title := video.Title + description := fmt.Sprintf("@%s's video on %s", authorHandle, brandingTitle) + metaTags = append(metaTags, + MetaTag{Type: "name", Key: "description", Content: description}, + MetaTag{Type: "property", Key: "og:description", Content: description}, + MetaTag{Type: "property", Key: "og:title", Content: title}, + MetaTag{Type: "name", Key: "twitter:description", Content: description}, + MetaTag{Type: "name", Key: "twitter:title", Content: title}, + ) return l.GenerateHTML(ctx, &PageConfig{ - Title: fmt.Sprintf("%s%s", titleStr, brandingTitle), + Title: title, Metas: metaTags, SentryDSN: sentryDSN, }) diff --git a/pkg/linking/linking_test.go b/pkg/linking/linking_test.go index e6b23de5..2b9c4132 100644 --- a/pkg/linking/linking_test.go +++ b/pkg/linking/linking_test.go @@ -105,7 +105,10 @@ func TestGenerateVideoCard(t *testing.T) { require.NoError(t, err) linkStr := string(linkCard) require.True(t, strings.Contains(linkStr, "iame.li"), "should contain the author handle") - require.True(t, strings.Contains(linkStr, video.Title), "should contain the video title") + require.True(t, strings.Contains(linkStr, ""+video.Title+""), + "page title should be the video's own title") + require.True(t, strings.Contains(linkStr, `content="`+video.Title+`"`), + "og:title/twitter:title should be the video's own title") require.True(t, strings.Contains(linkStr, "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:2zmxikig2sj7gqaezl5gntae/"+thumbCID+"@jpeg"), "og:image should be the video thumbnail served via the bsky CDN") diff --git a/pkg/model/video.go b/pkg/model/video.go index 5ac3de68..ae4f8436 100644 --- a/pkg/model/video.go +++ b/pkg/model/video.go @@ -114,12 +114,36 @@ func (m *DBModel) GetVideoView(ctx context.Context, uri string) (*streamplace.Me return nil, err } + tracks := []*streamplace.MediaTrack_TrackView{} + if rec.Source.MediaDefs_SourceTracks != nil { + for _, track := range rec.Source.MediaDefs_SourceTracks.Tracks { + t, err := m.GetMediaTrackByURI(ctx, track.Uri) + if err != nil { + return nil, fmt.Errorf("get media track by uri: %w", err) + } + if t == nil { + continue + } + cid, err := spid.GetCID(t) + if err != nil { + return nil, fmt.Errorf("get cid: %w", err) + } + tracks = append(tracks, &streamplace.MediaTrack_TrackView{ + Record: &lexutil.LexiconTypeDecoder{Val: t}, + Uri: track.Uri, + Cid: cid.String(), + Author: author, + }) + } + } + return &streamplace.MediaGetVideo_VideoView{ Uri: row.URI, Cid: row.CID, Author: author, Record: &lexutil.LexiconTypeDecoder{Val: rec}, ViewCounts: summary, + Tracks: tracks, }, nil } diff --git a/pkg/streamplace/mediagetVideo.go b/pkg/streamplace/mediagetVideo.go index 7f365c97..028b80e1 100644 --- a/pkg/streamplace/mediagetVideo.go +++ b/pkg/streamplace/mediagetVideo.go @@ -16,6 +16,7 @@ type MediaGetVideo_VideoView struct { Author *appbsky.ActorDefs_ProfileViewBasic `json:"author" cborgen:"author"` Cid string `json:"cid" cborgen:"cid"` Record *lexutil.LexiconTypeDecoder `json:"record" cborgen:"record"` + Tracks []*MediaTrack_TrackView `json:"tracks,omitempty" cborgen:"tracks,omitempty"` Uri string `json:"uri" cborgen:"uri"` // viewCounts: Aggregated view counts across every indexed reporter. Always present; zero-valued (count=0, reporters=0) when no place.stream.media.viewCount records have been observed yet, so consumers can render a count unconditionally. ViewCounts *MediaGetVideo_ViewCountSummary `json:"viewCounts" cborgen:"viewCounts"` diff --git a/pkg/streamplace/mediatrack.go b/pkg/streamplace/mediatrack.go index 35d00403..38558888 100644 --- a/pkg/streamplace/mediatrack.go +++ b/pkg/streamplace/mediatrack.go @@ -11,6 +11,7 @@ import ( "io" comatproto "github.com/bluesky-social/indigo/api/atproto" + appbsky "github.com/bluesky-social/indigo/api/bsky" lexutil "github.com/bluesky-social/indigo/lex/util" cbg "github.com/whyrusleeping/cbor-gen" ) @@ -149,3 +150,11 @@ func (t *MediaTrack_Track) UnmarshalCBOR(r io.Reader) error { return nil } } + +// MediaTrack_TrackView is a "trackView" in the place.stream.media.track schema. +type MediaTrack_TrackView struct { + Author *appbsky.ActorDefs_ProfileViewBasic `json:"author" cborgen:"author"` + Cid string `json:"cid" cborgen:"cid"` + Record *lexutil.LexiconTypeDecoder `json:"record" cborgen:"record"` + Uri string `json:"uri" cborgen:"uri"` +} -- 2.51.2