From edb60e184a9888486a1574aebd6882afb68f259d Mon Sep 17 00:00:00 2001 From: Pushpinder Singh Date: Wed, 30 Sep 2026 17:16:55 -0400 Subject: [PATCH 1/4] Reduce player resource usage and streamline builds --- .github/workflows/ci.yml | 44 ++- Dockerfile | 16 +- README.md | 129 +++--- cmd/restream/main.go | 2 +- internal/app/artwork_integration_test.go | 4 +- internal/app/config.go | 4 +- .../app/episodes_failure_integration_test.go | 6 +- .../app/library_refresh_integration_test.go | 2 +- internal/app/refresh_integration_test.go | 2 +- internal/app/server.go | 12 +- internal/app/store.go | 22 +- internal/stream/stream.go | 32 +- internal/stream/stream_test.go | 13 +- mise.toml | 12 +- web/assets/galaxy-clusters.prompt.txt | 4 - web/bun.lock | 73 +--- web/package.json | 13 +- web/playwright.config.ts | 18 +- web/public/galaxy-clusters.webp | Bin 75814 -> 0 bytes web/src/App.tsx | 176 ++++++--- web/src/components/galaxy-backdrop.tsx | 30 +- web/src/components/player-controls.tsx | 2 +- web/src/components/settings-page.tsx | 6 +- .../loading-button-dependencies.tsx | 125 ------ .../responsive-modal-dependencies.tsx | 144 ------- .../spectrumui/use-surface-theme.ts | 7 +- web/src/components/ui/button.tsx | 20 +- web/src/components/ui/input.tsx | 20 - web/src/components/ui/select.tsx | 83 +--- web/src/components/ui/tabs.tsx | 88 ----- web/src/styles.css | 371 +----------------- web/src/vite-env.d.ts | 1 + web/tests/real-playback.mjs | 215 +++++++++- web/tests/workflow.spec.ts | 164 +++++++- web/vite.config.ts | 18 + 35 files changed, 751 insertions(+), 1127 deletions(-) delete mode 100644 web/assets/galaxy-clusters.prompt.txt delete mode 100644 web/public/galaxy-clusters.webp delete mode 100644 web/src/components/spectrumui/loading-button-dependencies.tsx delete mode 100644 web/src/components/spectrumui/responsive-modal-dependencies.tsx delete mode 100644 web/src/components/ui/input.tsx delete mode 100644 web/src/components/ui/tabs.tsx create mode 100644 web/src/vite-env.d.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 914aeb7..8625045 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,21 +13,33 @@ env: IMAGE_NAME: ${{ github.repository }} jobs: -# test: -# name: Test -# if: github.event_name == 'pull_request' -# runs-on: ubuntu-latest -# -# steps: -# - name: Check out repository -# uses: actions/checkout@v7 -# -# - name: Install tools with mise -# uses: jdx/mise-action@v5 -# -# - name: Run mise test tasks -# shell: bash -# run: mise run test + test: + name: Test and build + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@v7 + - name: Install tools with mise + uses: jdx/mise-action@v5 + - name: Cache Go dependencies and builds + uses: actions/cache@v5 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-go-${{ hashFiles('go.mod', 'go.sum') }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-go-${{ hashFiles('go.mod', 'go.sum') }}- + - name: Cache frontend packages + uses: actions/cache@v5 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('web/bun.lock') }} + - name: Run integration tests and build frontend + run: mise run test ::: frontend publish: name: Build and publish image @@ -74,6 +86,8 @@ jobs: platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=restream + cache-to: type=gha,mode=max,scope=restream - name: Generate artifact attestation uses: actions/attest-build-provenance@v4 diff --git a/Dockerfile b/Dockerfile index 6f1836a..b1112c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,23 @@ -FROM oven/bun:1.4.0 AS frontend +# syntax=docker/dockerfile:1 +FROM --platform=$BUILDPLATFORM oven/bun:1.4.0 AS frontend WORKDIR /build/web COPY web/package.json web/bun.lock ./ -RUN bun install --frozen-lockfile -COPY web/ ./ +RUN --mount=type=cache,target=/root/.bun/install/cache bun install --frozen-lockfile +COPY web/index.html web/tsconfig*.json web/vite.config.ts ./ +COPY web/src/ ./src/ +COPY web/public/ ./public/ RUN bun run build -FROM golang:1.27-alpine3.24 AS backend +FROM --platform=$BUILDPLATFORM golang:1.27-alpine3.24 AS backend WORKDIR /build COPY go.mod go.sum ./ RUN go mod download COPY cmd/ ./cmd/ COPY internal/ ./internal/ -RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /restream ./cmd/restream +ARG TARGETOS +ARG TARGETARCH +RUN --mount=type=cache,target=/root/.cache/go-build \ + CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /restream ./cmd/restream FROM alpine:3.24 AS runtime LABEL org.opencontainers.image.title="Restream" \ diff --git a/README.md b/README.md index 84cf8de..0d9b8c2 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,92 @@ # Restream -Restream is a browser player for one Stalker/MAG IPTV portal. Watch live TV, movies, and series from devices on your network without installing an IPTV app on each one. +Restream is a web proxy for your Stalker/MAG IPTV provider. Multiple users can watch live TV, movies, and series from the same provider account in a web browser. -```text -Browsers <-- HTTP(S) --> Restream <-- Stalker portal --> Provider - | - +-- SQLite cache in /data - +-- FFmpeg --> HLS playback -``` - -## Run with Docker Compose - -```sh -git clone https://github.com/pushpinderbal/restream.git -cd restream -cp .env.example .env -``` - -Set `STALKER_PORTAL_URL` and `STALKER_MAC` in `.env` to your portal URL and registered MAC address, then run: +Each active player uses an upstream stream. Simultaneous viewing is limited by your provider's stream allocation and Restream's `MAX_STREAMS` setting. Paused players still count toward that limit. -```sh -docker compose up -d -``` +You need your provider's portal URL and registered MAC address, plus any serial number or device IDs your provider requires. -Compose pulls `ghcr.io/pushpinderbal/restream:latest`. Open `http://localhost:8080` or your server's LAN address. Keep `.env` private. See [.env.example](.env.example) for optional portal device identity and refresh settings. +## Run with Docker -Choose Live TV, Movies, or Series, browse or search, then open a title and press **Play**. - -Open **Settings** to check library and programme guide sync states, last successful and upcoming refreshes, guide coverage, and stream usage. Use the refresh icon beside each sync state to start a background refresh without interrupting playback. The icon spins and its state changes to **Syncing** while the refresh runs. A successful library refresh updates live channels and categories and expires cached movie, series, and episode lists. Open lists reload automatically; other lists fetch fresh data when next visited. Refreshes respect provider cooldowns. Intervals and playback limits are configured in `.env`. - -To update the published app: +Replace the example portal URL and MAC address with your provider's details: ```sh -docker compose pull -docker compose up -d +docker run -d \ + --name restream \ + --restart unless-stopped \ + --stop-timeout 20 \ + -p 8080:8080 \ + -v restream-data:/data \ + -e STALKER_PORTAL_URL='http://your-provider.example' \ + -e STALKER_MAC='00:11:22:33:44:55' \ + -e MAX_STREAMS=1 \ + ghcr.io/pushpinderbal/restream:latest ``` -## Develop with Docker Compose - -Use the standalone `compose.dev.yaml` for local development. Configure `.env` as above, then run from the repository root: - -```sh -docker compose -f compose.dev.yaml up --build --watch +## Or use Docker Compose + +Save this as `compose.yaml` and enter your provider's details in `environment`: + +```yaml +services: + restream: + image: ghcr.io/pushpinderbal/restream:latest + ports: + - "8080:8080" + environment: + STALKER_PORTAL_URL: "http://your-provider.example" + STALKER_MAC: "00:11:22:33:44:55" + STALKER_TIMEZONE: "UTC" + MAX_STREAMS: "1" + volumes: + - restream-data:/data + restart: unless-stopped + stop_grace_period: 20s + +volumes: + restream-data: ``` -Or use `mise run dev` if you have [mise](https://mise.jdx.dev/) installed. Development only requires Docker Compose 2.32 or newer; Go, Bun, and FFmpeg run inside the containers. - -Open `http://localhost:5173` or your development machine's LAN address on port 5173. The frontend proxies API requests and video playback to the development backend over the Compose network. - -- React and CSS edits hot reload from the mounted `web/` directory, without a container rebuild. -- Go source changes automatically rebuild and restart the backend through Compose Watch. Active playback stops when the backend restarts. -- Changes to `web/package.json` or `web/bun.lock` restart the frontend and reinstall dependencies. -- Development uses separate data and dependency volumes from the published app. The backend build skips the production UI bundle. - -Stop development with Ctrl+C, or remove its containers while retaining cached data: +Start it with: ```sh -docker compose -f compose.dev.yaml down +docker compose up -d ``` -The equivalent mise commands are `mise run dev:logs` and `mise run dev:down`. Plain `docker compose up -d` continues to run the published app on port 8080. Use the development file by itself, rather than combining it with `compose.yaml`. - -To work on the UI using a locally installed Bun instead, keep a backend running on `localhost:8080` and run `mise run dev:ui`, or run `bun install --frozen-lockfile` followed by `bun run dev` from `web/`. - -## How it works - -Restream caches live channels, guide data, categories, and requested movie and series pages in SQLite under `/data`. It fetches movie and series pages as people browse; it does not download the full catalog at startup. Movie and series pages expire after `CATALOG_REFRESH_INTERVAL` (default `24h`). Episode lists are shared after the first visit and expire independently after `EPISODE_CACHE_TTL` (default `1h`); reopening a series after expiry checks for new episodes. Successful manual and scheduled library refreshes expire these lists immediately while retaining title records used by active playback. Keep the `/data` volume to retain the cache across restarts. - -Each viewer uses a separate upstream stream. `MAX_STREAMS` (default: `1`) limits simultaneous players, including paused players; when all slots are in use, the browser shows a warning. Set the limit within your provider's allowance. - -Restream has no built-in client authentication. If you expose it beyond your LAN, put authentication in front of it and proxy the entire site, including `/api/streams/`. Run one container replica because playback slots and FFmpeg sessions are local to that container. +Open `http://localhost:8080`, or `http://:8080` from another device. Choose **Live TV**, **Movies**, or **Series**, open a title, and press **Play**. Use **Settings** to check or refresh the library and programme guide. Stop playback to release a stream for another viewer. + +Restream has no sign-in screen. Keep access private or protect it with authentication before making it available outside your network. + +## Environment variables + +Pass these with Docker's `-e` option or add them to the Compose `environment` section. Defaults below are for the Docker image and apply when a setting is omitted or empty. Duration values use units such as `250ms`, `45s`, `1m`, and `24h`. + +`-` means no default is supplied. + +| Variable | Default | Description | +| --- | --- | --- | +| `STALKER_PORTAL_URL` | - | (required) Your provider's portal URL, such as `http://your-provider.example`. | +| `STALKER_MAC` | - | (required) The MAC address registered with your provider. | +| `STALKER_TIMEZONE` | `UTC` | Timezone used with the provider, such as `America/Toronto`. | +| `STALKER_SERIAL_NUMBER` | - | Registered device serial number. Required only if your provider asks for it. | +| `STALKER_DEVICE_ID` | - | Registered device ID. Required only if your provider asks for it. | +| `STALKER_DEVICE_ID2` | - | Second registered device ID. Required only if your provider asks for it. | +| `STALKER_USER_AGENT` | Built-in MAG user agent | Override the device identification only if your provider requires a specific value. | +| `MAX_STREAMS` | `1` | Maximum simultaneous players. Increase only within your provider's allowance. Must be at least 1. | +| `CATALOG_REFRESH_INTERVAL` | `24h` | How often to refresh the library and how long to keep saved movie/series listings. Minimum `1m`. | +| `EPG_REFRESH_INTERVAL` | `6h` | How often to refresh the programme guide. Minimum `1m`. | +| `EPISODE_CACHE_TTL` | `1h` | How long to keep an episode list before checking for updates when it is opened again. Minimum `1m`. | +| `STALKER_EPG_HOURS` | `6` | Hours of programme guide information to request. Range: 1–168. | +| `STALKER_REQUEST_TIMEOUT` | `1m` | Maximum wait for a provider request. Minimum `1s`. | +| `STALKER_REQUEST_INTERVAL` | `250ms` | Minimum delay between provider requests. Minimum `100ms`. | +| `STALKER_MAX_RESPONSE_MB` | `64` | Maximum size of a provider response in MB. Range: 1–512. | +| `SESSION_TTL` | `45s` | Release a stream after its browser stops checking in. Minimum `30s`. | +| `TRANSCODE_MODE` | `auto` | `auto`: convert video when needed; `copy`: pass it through without conversion; `transcode`: always convert it. | +| `LISTEN_ADDR` | `:8080` | Address and port inside the container. Match the container port in your Docker port mapping if changed. | +| `DATA_DIR` | `/data` | Storage for saved library information and temporary playback files. Match your volume mount if changed. | +| `WEB_DIR` | `/app/web` | Location of the browser interface files. Change only if you provide those files at a different location. | + +FFmpeg and FFprobe are included in the Docker image and used automatically. ## License diff --git a/cmd/restream/main.go b/cmd/restream/main.go index 46b2b0f..8eb677c 100644 --- a/cmd/restream/main.go +++ b/cmd/restream/main.go @@ -36,7 +36,7 @@ func run() error { } var manager *stream.Manager if provider != nil { - manager, err = stream.New(stream.Config{MaxStreams: cfg.MaxStreams, SessionTTL: cfg.SessionTTL, DataDir: cfg.DataDir, FFmpegPath: cfg.FFmpegPath, FFprobePath: cfg.FFprobePath, TranscodeMode: cfg.TranscodeMode}, provider.Resolve) + manager, err = stream.New(stream.Config{MaxStreams: cfg.MaxStreams, SessionTTL: cfg.SessionTTL, DataDir: cfg.DataDir, TranscodeMode: cfg.TranscodeMode}, provider.Resolve) if err != nil { return err } diff --git a/internal/app/artwork_integration_test.go b/internal/app/artwork_integration_test.go index 0b7bbc3..7980add 100644 --- a/internal/app/artwork_integration_test.go +++ b/internal/app/artwork_integration_test.go @@ -156,11 +156,11 @@ func TestBrowseCacheUpgradePreservesOtherSavedData(t *testing.T) { t.Fatal(err) } q := model.BrowseQuery{Kind: "movie", Category: "7", Search: "literal", Page: 1} - if err := s.setBrowse(q, model.BrowsePage{Items: []model.Item{{ID: "movie:3", Kind: "movie", Name: "Cached film"}}, Page: 1}, time.Hour); err != nil { + if err := s.setBrowseAtRevision(q, model.BrowsePage{Items: []model.Item{{ID: "movie:3", Kind: "movie", Name: "Cached film"}}, Page: 1}, time.Hour, s.libraryRevision()); err != nil { t.Fatal(err) } deadline := time.Now().Add(2 * time.Minute) - if err := s.setRetryDeadline(false, deadline, true); err != nil { + if err := s.setPortalCooldown(deadline); err != nil { t.Fatal(err) } if _, err := s.db.Exec(`DELETE FROM meta WHERE key='browse_cache_version'`); err != nil { diff --git a/internal/app/config.go b/internal/app/config.go index 6c5482c..c7a9539 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -18,7 +18,7 @@ type Config struct { MaxStreams int SessionTTL, CatalogRefresh, EPGRefresh, RequestInterval time.Duration EpisodeCacheTTL time.Duration - FFmpegPath, FFprobePath, TranscodeMode string + TranscodeMode string } func LoadConfig() (Config, error) { @@ -26,7 +26,7 @@ func LoadConfig() (Config, error) { ListenAddr: env("LISTEN_ADDR", ":8080"), DataDir: env("DATA_DIR", "./data"), WebDir: env("WEB_DIR", "./web/dist"), PortalURL: strings.TrimSpace(os.Getenv("STALKER_PORTAL_URL")), MAC: strings.TrimSpace(os.Getenv("STALKER_MAC")), Timezone: env("STALKER_TIMEZONE", "UTC"), UserAgent: os.Getenv("STALKER_USER_AGENT"), SerialNumber: os.Getenv("STALKER_SERIAL_NUMBER"), DeviceID: os.Getenv("STALKER_DEVICE_ID"), DeviceID2: os.Getenv("STALKER_DEVICE_ID2"), - FFmpegPath: env("FFMPEG_PATH", "ffmpeg"), FFprobePath: env("FFPROBE_PATH", "ffprobe"), TranscodeMode: env("TRANSCODE_MODE", "auto"), + TranscodeMode: env("TRANSCODE_MODE", "auto"), } var err error responseMB, err := strconv.ParseInt(env("STALKER_MAX_RESPONSE_MB", "64"), 10, 32) diff --git a/internal/app/episodes_failure_integration_test.go b/internal/app/episodes_failure_integration_test.go index dd7e877..d31d7c9 100644 --- a/internal/app/episodes_failure_integration_test.go +++ b/internal/app/episodes_failure_integration_test.go @@ -42,7 +42,7 @@ func TestEpisodeFailureLoggedOnceWithoutPrivateDetails(t *testing.T) { } defer s.Close() q := model.BrowseQuery{Kind: "series", Category: "*", Page: 1} - if err := s.cache.setBrowse(q, model.BrowsePage{Items: []model.Item{{ID: "series:229631", Kind: "series", Name: "Cached series", ProviderID: "229631"}}, Page: 1}, time.Hour); err != nil { + if err := s.cache.setBrowseAtRevision(q, model.BrowsePage{Items: []model.Item{{ID: "series:229631", Kind: "series", Name: "Cached series", ProviderID: "229631"}}, Page: 1}, time.Hour, s.cache.libraryRevision()); err != nil { t.Fatal(err) } ts := httptest.NewServer(s) @@ -94,11 +94,11 @@ func TestCachedEpisodePlaybackRetainsProviderIdentityAfterRestart(t *testing.T) t.Fatal(err) } series := model.Item{ID: "series:229631", Kind: "series", Name: "Show", ProviderID: "229631"} - if err := cache.setBrowse(model.BrowseQuery{Kind: "series", Category: "*", Page: 1}, model.BrowsePage{Page: 1, Items: []model.Item{series}}, time.Hour); err != nil { + if err := cache.setBrowseAtRevision(model.BrowseQuery{Kind: "series", Category: "*", Page: 1}, model.BrowsePage{Page: 1, Items: []model.Item{series}}, time.Hour, cache.libraryRevision()); err != nil { t.Fatal(err) } episode := model.Item{ID: "episode:229631:16659:730024", Kind: "episode", Name: "Pilot", Season: 1, Episode: 1, ProviderID: "730024", SeriesID: "229631", EpisodeID: "730024"} - if err := cache.setEpisodes(series.ID, []model.Item{episode}); err != nil { + if err := cache.setEpisodesAtRevision(series.ID, []model.Item{episode}, cache.libraryRevision()); err != nil { t.Fatal(err) } if err := cache.close(); err != nil { diff --git a/internal/app/library_refresh_integration_test.go b/internal/app/library_refresh_integration_test.go index 7342f66..705cf1f 100644 --- a/internal/app/library_refresh_integration_test.go +++ b/internal/app/library_refresh_integration_test.go @@ -220,7 +220,7 @@ func TestEpisodeCacheExpiresIndependentlyOfSeriesPages(t *testing.T) { func TestRefreshDoesNotJoinOrCacheOlderListRequests(t *testing.T) { s, ts, p := newChangingLibrary(t, time.Hour) - if err := s.cache.setBrowse(model.BrowseQuery{Kind: "series", Category: "seed", Page: 1}, model.BrowsePage{Items: []model.Item{{ID: "series:2", Kind: "series"}}}, 24*time.Hour); err != nil { + if err := s.cache.setBrowseAtRevision(model.BrowseQuery{Kind: "series", Category: "seed", Page: 1}, model.BrowsePage{Items: []model.Item{{ID: "series:2", Kind: "series"}}}, 24*time.Hour, s.cache.libraryRevision()); err != nil { t.Fatal(err) } gate := make(chan struct{}) diff --git a/internal/app/refresh_integration_test.go b/internal/app/refresh_integration_test.go index 4316486..17cb15b 100644 --- a/internal/app/refresh_integration_test.go +++ b/internal/app/refresh_integration_test.go @@ -137,7 +137,7 @@ func TestSettingsManualRefreshLifecycle(t *testing.T) { t.Fatal("cross-origin refresh accepted", res.StatusCode) } cooldown := time.Now().Add(time.Hour) - if err := s.cache.setRetryDeadline(false, cooldown, true); err != nil { + if err := s.cache.setPortalCooldown(cooldown); err != nil { t.Fatal(err) } req, _ = http.NewRequest("POST", ts.URL+"/api/refresh", strings.NewReader(`{"target":"all"}`)) diff --git a/internal/app/server.go b/internal/app/server.go index e1892ba..25e6c37 100644 --- a/internal/app/server.go +++ b/internal/app/server.go @@ -95,6 +95,7 @@ func New(cfg Config, provider model.Provider, streams Streams) (*Server, error) } imageCache, err := ristretto.NewCache(&ristretto.Config[string, imageData]{NumCounters: 1024, MaxCost: 32 << 20, BufferItems: 64}) if err != nil { + _ = cache.close() return nil, err } ctx, cancel := context.WithCancel(context.Background()) @@ -111,7 +112,7 @@ func New(cfg Config, provider model.Provider, streams Streams) (*Server, error) until := cache.portalCooldownUntil cache.mu.RUnlock() cooldownProvider.ConfigureCooldown(until, func(next time.Time) error { - if err := cache.setRetryDeadline(false, next, true); err != nil { + if err := cache.setPortalCooldown(next); err != nil { return err } slog.Debug("Portal cooldown saved", "until", next, "wait", max(0, time.Until(next)).Round(time.Second)) @@ -548,7 +549,7 @@ func (s *Server) scheduler(catalog bool) { next = time.Now().Add(retryCooldown(err, failures)) var limited interface{ RetryDelay() time.Duration } if errors.As(err, &limited) && limited.RetryDelay() > 0 { - _ = s.cache.setRetryDeadline(false, next, true) + _ = s.cache.setPortalCooldown(next) } slog.Info("Refresh retry scheduled", "kind", kind, "nextAt", next, "failures", failures) } else { @@ -924,8 +925,11 @@ func (s *Server) fetchImage(rawURL string) (imageData, error) { return imageData{}, fmt.Errorf("image upstream returned %d", res.StatusCode) } data, err := io.ReadAll(io.LimitReader(res.Body, (5<<20)+1)) - if err != nil || len(data) > 5<<20 { - return imageData{}, fmt.Errorf("image too large or unreadable: %w", err) + if err != nil { + return imageData{}, fmt.Errorf("image unreadable: %w", err) + } + if len(data) > 5<<20 { + return imageData{}, errors.New("image too large") } mime := http.DetectContentType(data) switch mime { diff --git a/internal/app/store.go b/internal/app/store.go index 70075ff..b2f7bb8 100644 --- a/internal/app/store.go +++ b/internal/app/store.go @@ -674,9 +674,6 @@ func (s *store) browse(q model.BrowseQuery, ttl time.Duration) (model.BrowsePage } return page, true, nil } -func (s *store) setBrowse(q model.BrowseQuery, p model.BrowsePage, ttl time.Duration) error { - return s.setBrowseAtRevision(q, p, ttl, s.libraryRevision()) -} func (s *store) setBrowseAtRevision(q model.BrowseQuery, p model.BrowsePage, ttl time.Duration, revision time.Time) error { b, err := encodePage(p) @@ -715,10 +712,7 @@ func (s *store) setBrowseAtRevision(q model.BrowseQuery, p model.BrowsePage, ttl } return nil } -func (s *store) setRetryDeadline(_ bool, at time.Time, shared bool) error { - if !shared { - return nil - } +func (s *store) setPortalCooldown(at time.Time) error { s.mu.Lock() defer s.mu.Unlock() if !at.After(s.portalCooldownUntil) { @@ -738,9 +732,6 @@ func (s *store) setRetryDeadline(_ bool, at time.Time, shared bool) error { s.portalCooldownUntil = at return nil } -func (s *store) setEpisodes(id string, items []model.Item) error { - return s.setEpisodesAtRevision(id, items, s.libraryRevision()) -} func (s *store) setEpisodesAtRevision(id string, items []model.Item, revision time.Time) error { s.mu.Lock() @@ -788,17 +779,6 @@ func (s *store) getEpisodes(id string, ttl time.Duration) ([]model.Item, bool) { } return append([]model.Item(nil), items...), true } -func (s *store) cachedSeries() []model.Item { - s.mu.RLock() - defer s.mu.RUnlock() - out := make([]model.Item, 0, len(s.episodes)) - for id := range s.episodes { - if i, ok := s.byID[id]; ok && i.Kind == "series" { - out = append(out, i) - } - } - return out -} func (s *store) item(id string) (model.Item, bool) { s.mu.RLock() defer s.mu.RUnlock() diff --git a/internal/stream/stream.go b/internal/stream/stream.go index 92234aa..2999ce2 100644 --- a/internal/stream/stream.go +++ b/internal/stream/stream.go @@ -31,8 +31,6 @@ type Config struct { MaxStreams int SessionTTL time.Duration DataDir string - FFmpegPath string - FFprobePath string TranscodeMode string // auto, copy, transcode } @@ -89,12 +87,6 @@ func New(cfg Config, resolve Resolver) (*Manager, error) { if cfg.SessionTTL <= 0 { cfg.SessionTTL = 45 * time.Second } - if cfg.FFmpegPath == "" { - cfg.FFmpegPath = "ffmpeg" - } - if cfg.FFprobePath == "" { - cfg.FFprobePath = "ffprobe" - } if cfg.TranscodeMode == "" { cfg.TranscodeMode = "auto" } @@ -103,10 +95,10 @@ func New(cfg Config, resolve Resolver) (*Manager, error) { default: return nil, fmt.Errorf("%w: transcode mode", ErrInvalid) } - if _, err := exec.LookPath(cfg.FFmpegPath); err != nil { + if _, err := exec.LookPath("ffmpeg"); err != nil { return nil, fmt.Errorf("ffmpeg unavailable: %w", err) } - encoders, err := exec.Command(cfg.FFmpegPath, "-hide_banner", "-encoders").Output() + encoders, err := exec.Command("ffmpeg", "-hide_banner", "-encoders").Output() if err != nil { return nil, fmt.Errorf("cannot inspect ffmpeg encoders: %w", err) } @@ -255,8 +247,7 @@ func (m *Manager) Stop(id string) error { if e == nil { return ErrNotFound } - <-e.gen.done - _ = os.RemoveAll(e.gen.dir) + discard(e.gen) m.mu.Lock() m.stopping-- m.mu.Unlock() @@ -327,7 +318,12 @@ func (m *Manager) update(e *entry, g *generation, fn func(*Session)) { } } -func discard(g *generation) { <-g.done; _ = os.RemoveAll(g.dir) } +func discard(g *generation) { + <-g.done + _ = os.RemoveAll(g.dir) + // Remove the session directory only once every generation has been discarded. + _ = os.Remove(filepath.Dir(g.dir)) +} func (m *Manager) retire(g *generation) { discard(g); m.mu.Lock(); m.stopping--; m.mu.Unlock() } @@ -435,7 +431,7 @@ func (m *Manager) run(ctx context.Context, e *entry, g *generation, pos float64, args = append(args, "-hls_playlist_type", "event", "-hls_list_size", "0", "-hls_flags", "temp_file") } args = append(args, "-hls_segment_filename", filepath.Join(g.dir, "seg-%06d.ts"), filepath.Join(g.dir, "index.m3u8")) - cmd := exec.CommandContext(ctx, m.cfg.FFmpegPath, args...) + cmd := exec.CommandContext(ctx, "ffmpeg", args...) // Diagnostics can contain upstream URLs and authorization headers. Only classify them. stderr := &boundedOutput{limit: 16 * 1024} cmd.Stderr = stderr @@ -476,6 +472,7 @@ func (m *Manager) run(ctx context.Context, e *entry, g *generation, pos float64, case <-tick.C: if !ready && playable(g.dir) { ready = true + tick.Stop() startupTimer.Stop() m.update(e, g, func(s *Session) { s.State = "ready" }) } @@ -623,11 +620,6 @@ func validHeader(k, v string) bool { return true } -func (m *Manager) probe(ctx context.Context, source model.Source) (string, float64) { - mode, duration, _ := m.probeWithDiagnostics(ctx, source) - return mode, duration -} - func (m *Manager) probeWithDiagnostics(ctx context.Context, source model.Source) (string, float64, string) { pctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() @@ -643,7 +635,7 @@ func (m *Manager) probeWithDiagnostics(ctx context.Context, source model.Source) args = append(args, "-headers", hs.String()) } args = append(args, source.URL) - cmd := exec.CommandContext(pctx, m.cfg.FFprobePath, args...) + cmd := exec.CommandContext(pctx, "ffprobe", args...) stderr := &boundedOutput{limit: 16 * 1024} cmd.Stderr = stderr out, err := cmd.Output() diff --git a/internal/stream/stream_test.go b/internal/stream/stream_test.go index 7aa5eaf..26e5b58 100644 --- a/internal/stream/stream_test.go +++ b/internal/stream/stream_test.go @@ -69,10 +69,6 @@ func TestFFmpegSessionLifecycle(t *testing.T) { t.Fatal(err) } defer m.Close() - mode, probed := m.probe(context.Background(), model.Source{URL: upstream.URL + "/source.mp4"}) - if mode != "copy" || probed < 7.5 || probed > 8.5 { - t.Fatalf("probe: mode=%s duration=%v", mode, probed) - } item := model.Item{ID: "v", Kind: "movie", Duration: 60} s, err := m.Start(context.Background(), item, 0) if err != nil { @@ -88,6 +84,12 @@ func TestFFmpegSessionLifecycle(t *testing.T) { if s.Duration < 7.5 || s.Duration > 8.5 { t.Fatalf("probed duration: %v", s.Duration) } + m.mu.Lock() + probe := m.sessions[s.ID].probe + m.mu.Unlock() + if probe == nil || probe.mode != "copy" { + t.Fatalf("compatible source was not copied: %+v", probe) + } media := httptest.NewServer(m.MediaHandler()) defer media.Close() resp, err := http.Get(media.URL + s.URL) @@ -138,6 +140,9 @@ func TestFFmpegSessionLifecycle(t *testing.T) { if m.Active() != 0 { t.Fatal("slot not released") } + if _, err := os.Stat(filepath.Join(m.dir, s.ID)); !os.IsNotExist(err) { + t.Fatalf("stopped session files remain: %v", err) + } if _, err = m.Get(s.ID); !errors.Is(err, ErrNotFound) { t.Fatalf("get after stop: %v", err) } diff --git a/mise.toml b/mise.toml index 2de20a7..646625c 100644 --- a/mise.toml +++ b/mise.toml @@ -11,6 +11,16 @@ run = "docker compose -f compose.dev.yaml up --build --watch" description = "Stop the Docker development stack" run = "docker compose -f compose.dev.yaml down" +[tasks."dev:ui"] +description = "Run the UI locally against a backend on localhost:8080" +dir = "web" +run = ["bun install --frozen-lockfile", "bun run dev"] + +[tasks.frontend] +description = "Install locked frontend dependencies and build the production UI" +dir = "web" +run = ["bun install --frozen-lockfile", "bun run build"] + [tasks."dev:logs"] description = "Follow development container logs" run = "docker compose -f compose.dev.yaml logs -f" @@ -43,4 +53,4 @@ run = ["bun install --frozen-lockfile", "node node_modules/@playwright/test/cli. [tasks."test:playback"] description = "Run FFmpeg-to-browser integration test" depends = ["frontend"] -run = ["cd web && node node_modules/@playwright/test/cli.js install chromium", "RESTREAM_BROWSER_TESTS=1 go test ./internal/app -run TestPortalToBrowserPlayback -v"] +run = ["cd web && node node_modules/@playwright/test/cli.js install chromium", "RESTREAM_BROWSER_TESTS=1 go test ./internal/app -run '^TestPortalToBrowserPlayback$' -count=1 -v"] diff --git a/web/assets/galaxy-clusters.prompt.txt b/web/assets/galaxy-clusters.prompt.txt deleted file mode 100644 index 77eb372..0000000 --- a/web/assets/galaxy-clusters.prompt.txt +++ /dev/null @@ -1,4 +0,0 @@ -Generated with the built-in image_gen.imagegen tool on 2026-09-30. -App asset: web/public/galaxy-clusters.webp (WebP encoding of the generated image). - -Use case: stylized-concept. Asset type: dark full-page background texture for a streaming app, landscape 16:9. Primary request: a near-black deep-space starfield with clusters of distant galaxies and stars, glowing softly. Style: elegant realistic deep-space telescope photography. Several small and medium distant spiral galaxies with delicate spiral arms and luminous pearl-white cores, scattered asymmetrically across the image, surrounded by fine irregular star clusters. At least 80 percent of the image should read as almost black empty space, with muted indigo and violet dust and a little cool cyan light localized around galaxies. Keep galaxy clusters distinct and localized; no broad purple wash. Mostly tiny stars, just a few softly glowing brighter stars. Keep the center relatively sparse to let app text and tiles remain readable; distribute galaxies across the outer and middle portions so some remain visible in portrait crops. Deep blacks, restrained highlights, fine detail, calm infinite depth. No text, logos, borders, planets, spaceships, UI, huge central galaxy, bright nebula wall, or lens flares. diff --git a/web/bun.lock b/web/bun.lock index fcfd834..bbbba1b 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -5,14 +5,15 @@ "": { "name": "restream-web", "dependencies": { - "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-select": "2.3.7", + "@radix-ui/react-slider": "1.4.7", + "@radix-ui/react-slot": "1.3.3", "border-beam": "1.4.1", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "hls.js": "1.6.13", "lucide-react": "1.48.0", "motion": "^13.4.4", - "radix-ui": "1.6.7", "react": "19.1.1", "react-dom": "19.1.1", "tailwind-merge": "3.7.0", @@ -147,62 +148,24 @@ "@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="], - "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.15", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A=="], - - "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.20", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collapsible": "1.1.20", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg=="], - - "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.23", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dialog": "1.1.23", "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA=="], - "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.15", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA=="], - "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.15", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ=="], - - "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA=="], - - "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ=="], - - "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.20", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q=="], - "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.15", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA=="], "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.5", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA=="], "@radix-ui/react-context": ["@radix-ui/react-context@1.2.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA=="], - "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.3.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew=="], - - "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.23", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA=="], - "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg=="], "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w=="], - "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.24", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g=="], - "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.6", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ=="], "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.16", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ=="], - "@radix-ui/react-form": ["@radix-ui/react-form@0.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-label": "2.1.15", "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA=="], - - "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.23", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ=="], - "@radix-ui/react-id": ["@radix-ui/react-id@1.1.4", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA=="], - "@radix-ui/react-label": ["@radix-ui/react-label@2.1.15", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g=="], - - "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.24", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-callback-ref": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA=="], - - "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.24", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA=="], - - "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.22", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-previous": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg=="], - - "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.16", "", { "dependencies": { "@radix-ui/number": "1.1.3", "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-effect-event": "0.0.5", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw=="], - - "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-effect-event": "0.0.5", "@radix-ui/react-use-is-hydrated": "0.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw=="], - - "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.23", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ=="], - "@radix-ui/react-popper": ["@radix-ui/react-popper@1.3.7", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-rect": "1.1.4", "@radix-ui/react-use-size": "1.1.4", "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg=="], "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.17", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ=="], @@ -211,46 +174,18 @@ "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.10", "", { "dependencies": { "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg=="], - "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.16", "", { "dependencies": { "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg=="], - - "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.4.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ=="], - - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ=="], - - "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.18", "", { "dependencies": { "@radix-ui/number": "1.1.3", "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA=="], - "@radix-ui/react-select": ["@radix-ui/react-select@2.3.7", "", { "dependencies": { "@radix-ui/number": "1.1.3", "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-previous": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg=="], - "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.15", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw=="], - "@radix-ui/react-slider": ["@radix-ui/react-slider@1.4.7", "", { "dependencies": { "@radix-ui/number": "1.1.3", "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-previous": "1.1.4", "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg=="], "@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q=="], - "@radix-ui/react-switch": ["@radix-ui/react-switch@1.3.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw=="], - - "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.21", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog=="], - - "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.23", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg=="], - - "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.18", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug=="], - - "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-toggle": "1.1.18", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA=="], - - "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-separator": "1.1.15", "@radix-ui/react-toggle-group": "1.1.19" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw=="], - - "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg=="], - "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ=="], "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-use-effect-event": "0.0.5", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ=="], "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.5", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg=="], - "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.5", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA=="], - - "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw=="], - "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw=="], "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg=="], @@ -469,8 +404,6 @@ "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], - "radix-ui": ["radix-ui@1.6.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-accessible-icon": "1.1.15", "@radix-ui/react-accordion": "1.2.20", "@radix-ui/react-alert-dialog": "1.1.23", "@radix-ui/react-arrow": "1.1.15", "@radix-ui/react-aspect-ratio": "1.1.15", "@radix-ui/react-avatar": "1.2.6", "@radix-ui/react-checkbox": "1.3.11", "@radix-ui/react-collapsible": "1.1.20", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-context-menu": "2.3.7", "@radix-ui/react-dialog": "1.1.23", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-dropdown-menu": "2.1.24", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-form": "0.1.16", "@radix-ui/react-hover-card": "1.1.23", "@radix-ui/react-label": "2.1.15", "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-menubar": "1.1.24", "@radix-ui/react-navigation-menu": "1.2.22", "@radix-ui/react-one-time-password-field": "0.1.16", "@radix-ui/react-password-toggle-field": "0.1.11", "@radix-ui/react-popover": "1.1.23", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-progress": "1.1.16", "@radix-ui/react-radio-group": "1.4.7", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-scroll-area": "1.2.18", "@radix-ui/react-select": "2.3.7", "@radix-ui/react-separator": "1.1.15", "@radix-ui/react-slider": "1.4.7", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-switch": "1.3.7", "@radix-ui/react-tabs": "1.1.21", "@radix-ui/react-toast": "1.2.23", "@radix-ui/react-toggle": "1.1.18", "@radix-ui/react-toggle-group": "1.1.19", "@radix-ui/react-toolbar": "1.1.19", "@radix-ui/react-tooltip": "1.2.16", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-effect-event": "0.0.5", "@radix-ui/react-use-escape-keydown": "1.1.5", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-size": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA=="], - "react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="], "react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="], diff --git a/web/package.json b/web/package.json index 8395ff4..10d5843 100644 --- a/web/package.json +++ b/web/package.json @@ -6,20 +6,21 @@ "packageManager": "bun@1.4.0", "type": "module", "scripts": { - "dev": "bunx --bun vite --host 0.0.0.0", - "build": "bunx --bun tsc -b && bunx --bun vite build", - "preview": "bunx --bun vite preview --host 0.0.0.0", - "test:browser": "node node_modules/@playwright/test/cli.js test" + "dev": "vite --host 0.0.0.0", + "build": "tsc -b && vite build", + "preview": "vite preview --host 0.0.0.0", + "test:browser": "playwright test" }, "dependencies": { - "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-select": "2.3.7", + "@radix-ui/react-slider": "1.4.7", + "@radix-ui/react-slot": "1.3.3", "border-beam": "1.4.1", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "hls.js": "1.6.13", "lucide-react": "1.48.0", "motion": "^13.4.4", - "radix-ui": "1.6.7", "react": "19.1.1", "react-dom": "19.1.1", "tailwind-merge": "3.7.0" diff --git a/web/playwright.config.ts b/web/playwright.config.ts index b72bb38..d0646fd 100644 --- a/web/playwright.config.ts +++ b/web/playwright.config.ts @@ -1,18 +1,28 @@ import { defineConfig } from "@playwright/test"; +const port = Number(process.env.PLAYWRIGHT_PORT || 4174); +const baseURL = `http://127.0.0.1:${port}`; +const build = + process.env.PLAYWRIGHT_SKIP_BUILD === "true" ? "" : "bun run build && "; + export default defineConfig({ testDir: "./tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + workers: process.env.PLAYWRIGHT_WORKERS + ? Number(process.env.PLAYWRIGHT_WORKERS) + : 2, use: { - baseURL: "http://127.0.0.1:5173", + baseURL, browserName: "chromium", launchOptions: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH ? { executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH } : {}, }, webServer: { - command: "bun run dev -- --host 127.0.0.1", - url: "http://127.0.0.1:5173", - reuseExistingServer: true, + command: `${build}bun run preview -- --host 127.0.0.1 --port ${port} --strictPort`, + url: baseURL, + reuseExistingServer: false, }, reporter: "list", }); diff --git a/web/public/galaxy-clusters.webp b/web/public/galaxy-clusters.webp deleted file mode 100644 index 51d48f0c28072dcafc132195d88c739f4b3f4f43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75814 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 diff --git a/web/src/App.tsx b/web/src/App.tsx index 5abdc0d..2fd64f1 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,4 +1,6 @@ import { + lazy, + Suspense, useCallback, useEffect, useLayoutEffect, @@ -9,7 +11,8 @@ import { type RefObject, } from "react"; import { createPortal } from "react-dom"; -import Hls from "hls.js"; +import type Hls from "hls.js"; +import hlsWorkerUrl from "hls.js/dist/hls.worker.js?url"; import { AnimatePresence, LayoutGroup, @@ -31,7 +34,7 @@ import { Settings, Square, } from "lucide-react"; -import { SettingsPage, type RefreshTarget } from "./components/settings-page"; +import type { RefreshTarget } from "./components/settings-page"; import { PlayerControls } from "./components/player-controls"; import { GalaxyBackdrop } from "./components/galaxy-backdrop"; import { Button } from "./components/ui/button"; @@ -59,6 +62,12 @@ import { type Status, } from "./api"; +const SettingsPage = lazy(() => + import("./components/settings-page").then((module) => ({ + default: module.SettingsPage, + })), +); + type Tab = "live" | "movie" | "series"; type PlaybackSelection = { item: Item; title: Item }; type EpisodeDestination = { @@ -833,7 +842,7 @@ function App() { return (
- + Skip to content @@ -915,12 +924,14 @@ function App() {
{settingsOpen && ( - + }> + + )}