// Package viewlog captures playback request events (manifest + // segment fetches) to a blob.Store as gzipped JSONL files, so a later // aggregation pass can derive view counts per place.stream.video. // // Goals at this layer: cheap to call from request handlers, non- // blocking, durable enough that a periodic flush survives a crash // with bounded data loss (the in-memory buffer between flushes). // // Privacy: IPs are HMAC'd with a daily-rotated salt held in localdb. // Same IP collides within a UTC day (useful for dedup); the salt // changes at midnight so cross-day correlation requires the original // salt, which the operator can prune to enforce a retention horizon. package viewlog import "time" // Event type tags. const ( EventTypeManifestRequest = "manifest_request" EventTypeSegmentRequest = "segment_request" ) // Manifest kinds populated on ManifestRequest events. const ( ManifestKindMaster = "master" ManifestKindMedia = "media" ) // Event is one line in the JSONL output. The Type field selects which // of the per-type field groups are meaningful — empty fields are // omitted on the wire to keep files small. type Event struct { // Ts is the server's UTC clock at request time. Ts time.Time `json:"ts"` // Type is one of EventType*. Aggregators dispatch on this. Type string `json:"type"` // SID is the playback session id (atproto TID) the player carries // across manifest + segment fetches. Generated by the server on // the master playlist response and threaded through sub-playlist // + segment URLs from there. SID string `json:"sid,omitempty"` // IPHash is hex(HMAC-SHA256(daily_salt, ip)). Same IP, same UTC // day, same hash; salt rotation at midnight breaks correlation // across days. Empty when no IP could be resolved. IPHash string `json:"ip_hash,omitempty"` // --- manifest_request --- // VideoURI is the place.stream.video AT-URI the manifest is for. VideoURI string `json:"video_uri,omitempty"` // ManifestKind is "master" or "media". ManifestKind string `json:"manifest_kind,omitempty"` // Track is the muxlTrack id when ManifestKind == "media"; empty // for master playlists (those span every track). Track string `json:"track,omitempty"` // --- segment_request --- // CID is the content-addressed blob requested. For a playback // segment this is the MUXL container blob shared across the // video's tracks; for init segments it's the per-track init // blob. Aggregators join CID → place.stream.video via the // MediaTrack index. CID string `json:"cid,omitempty"` // OwnerDID is the repo DID the segment URL claims as the owner. // Lets the aggregator attribute traffic to a creator without a // CID lookup; verified upstream at the playback handler. OwnerDID string `json:"owner_did,omitempty"` // RangeStart / RangeEnd capture the HTTP Range bytes, when the // request asked for one. End is inclusive (matches RFC 7233). // Zero values indicate "whole blob" / "open-ended" range. RangeStart int64 `json:"range_start,omitempty"` RangeEnd int64 `json:"range_end,omitempty"` }