···1+package bus
2+3+import (
4+ "context"
5+ "fmt"
6+ "time"
7+8+ "go.opentelemetry.io/otel"
9+ "stream.place/streamplace/pkg/log"
10+ "stream.place/streamplace/pkg/spmetrics"
11+)
12+13+// it's a segment channel manager, you see
14+15+type Seg struct {
16+ Filepath string
17+ Data []byte
18+ PacketizedData *PacketizedSegment
19+}
20+21+type PacketizedSegment struct {
22+ Video [][]byte
23+ Audio [][]byte
24+ Duration time.Duration
25+}
26+27+var chanSize = 1024
28+29+type SegChan struct {
30+ C chan *Seg
31+ Context context.Context
32+}
33+34+var bufSize = 10
35+36+func segChanKey(user string, rendition string) string {
37+ return fmt.Sprintf("%s::%s", user, rendition)
38+}
39+40+// get a channel to subscribe to new segments for a given user and rendition
41+func (b *Bus) SubscribeSegment(ctx context.Context, user string, rendition string) *SegChan {
42+ return b.SubscribeSegmentBuf(ctx, user, rendition, 0)
43+}
44+45+// get a channel to subscribe to new segments for a given user and rendition,
46+// starting with bufSize cached segments that we already have
47+func (b *Bus) SubscribeSegmentBuf(ctx context.Context, user string, rendition string, bufSize int) *SegChan {
48+ key := segChanKey(user, rendition)
49+ b.segChansMutex.Lock()
50+ defer b.segChansMutex.Unlock()
51+ chs, ok := b.segChans[key]
52+ if !ok {
53+ chs = []*SegChan{}
54+ b.segChans[key] = chs
55+ }
56+ ch := make(chan *Seg)
57+ b.segBufMutex.RLock()
58+ defer b.segBufMutex.RUnlock()
59+ curBuf, ok := b.segBuf[key]
60+ myCh := make(chan *Seg, chanSize)
61+ if ok {
62+ if bufSize > len(curBuf) {
63+ bufSize = len(curBuf)
64+ }
65+ for i := 0; i < bufSize; i += 1 {
66+ myCh <- curBuf[len(curBuf)-bufSize+i]
67+ }
68+ }
69+ segChan := &SegChan{C: ch, Context: ctx}
70+ chs = append(chs, segChan)
71+ b.segChans[key] = chs
72+ spmetrics.SegmentSubscriptionsOpen.WithLabelValues(user, rendition).Set(float64(len(chs)))
73+ return segChan
74+}
75+76+// unsubscribe from a channel for a given user and rendition
77+func (b *Bus) UnsubscribeSegment(ctx context.Context, user string, rendition string, ch *SegChan) {
78+ key := segChanKey(user, rendition)
79+ b.segChansMutex.Lock()
80+ defer b.segChansMutex.Unlock()
81+ chs, ok := b.segChans[key]
82+ if !ok {
83+ return
84+ }
85+ for i, c := range chs {
86+ if c == ch {
87+ chs = append(chs[:i], chs[i+1:]...)
88+ break
89+ }
90+ }
91+ spmetrics.SegmentSubscriptionsOpen.WithLabelValues(user, rendition).Set(float64(len(chs)))
92+ b.segChans[key] = chs
93+}
94+95+func (b *Bus) PublishSegment(ctx context.Context, user string, rendition string, seg *Seg) {
96+ ctx, span := otel.Tracer("signer").Start(ctx, "PublishSegment")
97+ defer span.End()
98+ key := segChanKey(user, rendition)
99+ b.segChansMutex.Lock()
100+ defer b.segChansMutex.Unlock()
101+ b.segBufMutex.Lock()
102+ defer b.segBufMutex.Unlock()
103+ curBuf, ok := b.segBuf[key]
104+ if !ok {
105+ curBuf = []*Seg{}
106+ b.segBuf[key] = curBuf
107+ }
108+ curBuf = append(curBuf, seg)
109+ if len(curBuf) > bufSize {
110+ curBuf = curBuf[1:]
111+ }
112+ b.segBuf[key] = curBuf
113+ chs, ok := b.segChans[key]
114+ if !ok {
115+ return
116+ }
117+ for _, ch := range chs {
118+ go func(segChan *SegChan) {
119+ select {
120+ case segChan.C <- seg:
121+ case <-segChan.Context.Done():
122+ return
123+ case <-time.After(1 * time.Minute):
124+ log.Warn(ctx, "failed to send segment to channel, timing out", "user", user, "rendition", rendition)
125+ }
126+127+ }(ch)
128+ }
129+}
+1-58
pkg/cmd/streamplace.go
···116 }
117 _ = flag.Set("logtostderr", "true")
118 vFlag := flag.Lookup("v")
119- fs := flag.NewFlagSet("streamplace", flag.ExitOnError)
120 cli := config.CLI{Build: build}
121- fs.StringVar(&cli.DataDir, "data-dir", config.DefaultDataDir(), "directory for keeping all streamplace data")
122- fs.StringVar(&cli.HTTPAddr, "http-addr", ":38080", "Public HTTP address")
123- fs.StringVar(&cli.HTTPInternalAddr, "http-internal-addr", "127.0.0.1:39090", "Private, admin-only HTTP address")
124- fs.StringVar(&cli.HTTPSAddr, "https-addr", ":38443", "Public HTTPS address")
125- fs.BoolVar(&cli.Secure, "secure", false, "Run with HTTPS. Required for WebRTC output")
126- cli.DataDirFlag(fs, &cli.TLSCertPath, "tls-cert", filepath.Join("tls", "tls.crt"), "Path to TLS certificate")
127- cli.DataDirFlag(fs, &cli.TLSKeyPath, "tls-key", filepath.Join("tls", "tls.key"), "Path to TLS key")
128- fs.StringVar(&cli.SigningKeyPath, "signing-key", "", "Path to signing key for pushing OTA updates to the app")
129- cli.DataDirFlag(fs, &cli.DBPath, "db-path", "db.sqlite", "path to sqlite database file")
130- fs.StringVar(&cli.AdminAccount, "admin-account", "", "ethereum account that administrates this streamplace node")
131- fs.StringVar(&cli.FirebaseServiceAccount, "firebase-service-account", "", "JSON string of a firebase service account key")
132- fs.StringVar(&cli.GitLabURL, "gitlab-url", "https://git.stream.place/api/v4/projects/1", "gitlab url for generating download links")
133- cli.DataDirFlag(fs, &cli.EthKeystorePath, "eth-keystore-path", "keystore", "path to ethereum keystore")
134- fs.StringVar(&cli.EthAccountAddr, "eth-account-addr", "", "ethereum account address to use (if keystore contains more than one)")
135- fs.StringVar(&cli.EthPassword, "eth-password", "", "password for encrypting keystore")
136- fs.StringVar(&cli.TAURL, "ta-url", "http://timestamp.digicert.com", "timestamp authority server for signing")
137- fs.StringVar(&cli.PKCS11ModulePath, "pkcs11-module-path", "", "path to a PKCS11 module for HSM signing, for example /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so")
138- fs.StringVar(&cli.PKCS11Pin, "pkcs11-pin", "", "PIN for logging into PKCS11 token. if not provided, will be prompted interactively")
139- fs.StringVar(&cli.PKCS11TokenSlot, "pkcs11-token-slot", "", "slot number of PKCS11 token (only use one of slot, label, or serial)")
140- fs.StringVar(&cli.PKCS11TokenLabel, "pkcs11-token-label", "", "label of PKCS11 token (only use one of slot, label, or serial)")
141- fs.StringVar(&cli.PKCS11TokenSerial, "pkcs11-token-serial", "", "serial number of PKCS11 token (only use one of slot, label, or serial)")
142- fs.StringVar(&cli.PKCS11KeypairLabel, "pkcs11-keypair-label", "", "label of signing keypair on PKCS11 token")
143- fs.StringVar(&cli.PKCS11KeypairID, "pkcs11-keypair-id", "", "id of signing keypair on PKCS11 token")
144- fs.StringVar(&cli.AppBundleID, "app-bundle-id", "", "bundle id of an app that we facilitate oauth login for")
145- fs.StringVar(&cli.StreamerName, "streamer-name", "", "name of the person streaming from this streamplace node")
146- fs.StringVar(&cli.FrontendProxy, "dev-frontend-proxy", "", "(FOR DEVELOPMENT ONLY) proxy frontend requests to this address instead of using the bundled frontend")
147- fs.StringVar(&cli.LivepeerGatewayURL, "livepeer-gateway-url", "", "URL of the Livepeer Gateway to use for transcoding")
148- fs.BoolVar(&cli.WideOpen, "wide-open", false, "allow ALL streams to be uploaded to this node (not recommended for production)")
149- cli.StringSliceFlag(fs, &cli.AllowedStreams, "allowed-streams", "", "if set, only allow these addresses or atproto DIDs to upload to this node")
150- cli.StringSliceFlag(fs, &cli.Peers, "peers", "", "other streamplace nodes to replicate to")
151- cli.StringSliceFlag(fs, &cli.Redirects, "redirects", "", "http 302s /path/one:/path/two,/path/three:/path/four")
152- cli.DebugFlag(fs, &cli.Debug, "debug", "", "modified log verbosity for specific functions or files in form func=ToHLS:3,file=gstreamer.go:4")
153- fs.BoolVar(&cli.TestStream, "test-stream", false, "run a built-in test stream on boot")
154- fs.BoolVar(&cli.NoFirehose, "no-firehose", false, "disable the bluesky firehose")
155- fs.BoolVar(&cli.PrintChat, "print-chat", false, "print chat messages to stdout")
156- fs.StringVar(&cli.WHIPTest, "whip-test", "", "run a WHIP self-test with the given parameters")
157 verbosity := fs.String("v", "3", "log verbosity level")
158- fs.StringVar(&cli.RelayHost, "relay-host", "wss://bsky.network", "websocket url for relay firehose")
159- fs.Bool("insecure", false, "DEPRECATED, does nothing.")
160- fs.StringVar(&cli.Color, "color", "", "'true' to enable colorized logging, 'false' to disable")
161- fs.StringVar(&cli.PublicHost, "public-host", "", "public host for this streamplace node (excluding https:// e.g. stream.place)")
162- fs.BoolVar(&cli.Thumbnail, "thumbnail", true, "enable thumbnail generation")
163- fs.BoolVar(&cli.SmearAudio, "smear-audio", false, "enable audio smearing to create 'perfect' segment timestamps")
164- fs.BoolVar(&cli.ExternalSigning, "external-signing", false, "enable external signing via exec (prevents potential memory leak)")
165- fs.StringVar(&cli.TracingEndpoint, "tracing-endpoint", "", "gRPC endpoint to send traces to")
166- fs.IntVar(&cli.RateLimitPerSecond, "rate-limit-per-second", 0, "rate limit for requests per second per ip")
167- fs.IntVar(&cli.RateLimitBurst, "rate-limit-burst", 0, "rate limit burst for requests per ip")
168- fs.IntVar(&cli.RateLimitWebsocket, "rate-limit-websocket", 10, "number of concurrent websocket connections allowed per ip")
169- fs.StringVar(&cli.RTMPServerAddon, "rtmp-server-addon", "", "address of external RTMP server to forward streams to")
170- fs.StringVar(&cli.RtmpsAddr, "rtmps-addr", ":1935", "address to listen for RTMPS connections")
171- cli.JSONFlag(fs, &cli.DiscordWebhooks, "discord-webhooks", "[]", "JSON array of Discord webhooks to send notifications to")
172 version := fs.Bool("version", false, "print version and exit")
173-174- if runtime.GOOS == "linux" {
175- fs.BoolVar(&cli.NoMist, "no-mist", true, "Disable MistServer")
176- fs.IntVar(&cli.MistAdminPort, "mist-admin-port", 14242, "MistServer admin port (internal use only)")
177- fs.IntVar(&cli.MistRTMPPort, "mist-rtmp-port", 11935, "MistServer RTMP port (internal use only)")
178- fs.IntVar(&cli.MistHTTPPort, "mist-http-port", 18080, "MistServer HTTP port (internal use only)")
179- }
180181 err = cli.Parse(
182 fs, os.Args[1:],