BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: media player & downloading

+173
+113
docs/specs/media.md
··· 1 + # Media Viewer & Downloads 2 + 3 + In-app media viewing and downloading for images and videos embedded in posts. 4 + 5 + ## Video Player 6 + 7 + BlueSky videos are HLS streams. The `app.bsky.embed.video#view` embed provides a `playlist` URL (m3u8 manifest) and an optional `thumbnail`. 8 + 9 + ### Playback 10 + 11 + - Use the native `<video>` element with HLS.js for manifest parsing (Safari handles HLS natively; HLS.js covers Chromium/WebKit in Tauri). 12 + - Inline player replaces the current thumbnail-as-external-link treatment in `EmbedContent`. 13 + - Controls: play/pause, progress scrubber, volume, fullscreen. Use browser-native controls (`controls` attribute) initially — custom controls are a future polish item. 14 + - Muted autoplay is **off** by default. Player shows thumbnail with a centered play button overlay; playback starts on click. 15 + - Respect `aspectRatio` from the embed to size the player container and prevent layout shift. 16 + - `alt` text from the embed is rendered below the player as a caption when present. 17 + 18 + ### Fullscreen 19 + 20 + - Clicking a "fullscreen" control (or double-click on the player) enters native fullscreen via the Fullscreen API. 21 + - `Escape` exits fullscreen (browser default). 22 + 23 + ## Image Gallery 24 + 25 + Clicking any image in an `ImageEmbed` opens a full-window overlay gallery. 26 + 27 + ### Overlay 28 + 29 + - Glass overlay: `surface_container_highest` at 70% opacity + `backdrop-blur: 20px`. 30 + - The selected image is displayed at its natural resolution (`fullsize` URL), constrained to viewport with `object-contain`. 31 + - `Presence` fade-in on open, fade-out on close. 32 + 33 + ### Navigation 34 + 35 + - Left/right arrows (keyboard and on-screen chevron buttons) cycle through images in the post. 36 + - Indicators (dots or `1/4` counter) show position in the set. 37 + - Single-image posts show no navigation controls. 38 + 39 + ### Caption 40 + 41 + Below the image, display: 42 + - **Alt text** from the image embed (primary caption, `body-md`). 43 + - **Post text** (secondary, `label-sm`, `on_surface_variant`, truncated to 2 lines with "show more" expansion). 44 + - **Author handle** linking to their profile. 45 + 46 + ### Keyboard 47 + 48 + | Key | Action | 49 + | ----------------- | ------------------- | 50 + | `Escape` | Close gallery | 51 + | `ArrowLeft` | Previous image | 52 + | `ArrowRight` | Next image | 53 + 54 + ### Gestures (future) 55 + 56 + Pinch-to-zoom and swipe navigation are deferred to a future milestone. 57 + 58 + ## Downloads 59 + 60 + Users can download images and videos to their local filesystem. 61 + 62 + ### Download Directory 63 + 64 + - Default: `~/Downloads`. 65 + - Configurable via a new `download_directory` setting in `app_settings`. 66 + - Settings UI: a path input with a "Browse" button that opens Tauri's directory picker dialog (`dialog.open` with `directory: true`). 67 + - The backend validates that the chosen path exists and is writable before persisting. 68 + 69 + ### Setting 70 + 71 + | Key | Type | Default | Description | 72 + | -------------------- | ------ | -------------- | ---------------------------- | 73 + | `download_directory` | string | `~/Downloads` | Target directory for saves | 74 + 75 + ### Triggering Downloads 76 + 77 + - **Images**: a download button (icon: `i-ri-download-2-line`) appears in the gallery overlay toolbar. Downloads the `fullsize` URL. Also available via right-click context menu on inline images. 78 + - **Videos**: a download button in the video player controls area. Downloads the HLS stream — the backend fetches the m3u8 manifest, resolves the highest-quality variant, downloads all segments, and muxes into a single MP4 file. 79 + 80 + ### Backend 81 + 82 + Video download requires server-side work because HLS streams are segmented: 83 + 84 + ```rust 85 + // Download a media file (image or video) to the configured download directory. 86 + // For images: direct HTTP fetch of the source URL. 87 + // For videos: fetch m3u8 manifest, download segments, concatenate into MP4. 88 + download_media(url: String, media_type: MediaType, filename: Option<String>) -> DownloadResult 89 + ``` 90 + 91 + - `MediaType`: `Image` or `Video`. 92 + - `DownloadResult`: `{ path: String, bytes: u64 }`. 93 + - Filename: derived from URL path if not provided. Collision handling: append `_1`, `_2`, etc. 94 + - The command should emit progress events (`download-progress`) for large video files so the frontend can show a progress indicator. 95 + 96 + ### Frontend UX 97 + 98 + - Download button shows a brief spinner/progress indicator while active. 99 + - On completion: success toast with the filename and "Open in Finder" action (uses `tauri-plugin-opener`). 100 + - On failure: error toast with a human-readable message ("Couldn't save — check that the download folder exists"). 101 + 102 + ## Tauri Capabilities 103 + 104 + The following permissions are needed beyond what `default.json` currently grants: 105 + 106 + - `dialog:default` — for the directory picker in settings. 107 + - `fs:default` — for writing downloaded files to disk (scoped to the user's download directory). 108 + 109 + ## Constraints 110 + 111 + - HLS.js is a runtime dependency (~60 KB gzipped). It should be lazy-loaded only when a video embed is in view. 112 + - Video muxing on the backend uses raw segment concatenation for MPEG-TS streams. If the CDN serves fMP4 segments, a lightweight remux step is needed — evaluate `mp4` or `ffmpeg-sidecar` crates at implementation time. 113 + - Downloads are not queued or batched in v1. One download at a time; concurrent downloads are a future enhancement.
+60
docs/tasks/15-media.md
··· 1 + # Milestone 15: Media Viewer & Downloads 2 + 3 + Spec: [media.md](../specs/media.md) 4 + 5 + Depends on: Milestone 03 (Feeds — PostCard, EmbedContent), Milestone 06 (Settings) 6 + 7 + ## Steps 8 + 9 + ### Backend - `src-tauri/src/media.rs` + `src-tauri/src/commands/media.rs` 10 + 11 + - [ ] Add `DownloadDirectory` variant to `SettingsKey` enum, default to `~/Downloads` via `dirs::download_dir()` 12 + - [ ] `get_download_directory()` — resolve current download path (setting or OS default), validate it exists 13 + - [ ] `set_download_directory(path: String)` — validate path is a writable directory, persist to `app_settings` 14 + - [ ] `download_image(url: String, filename: Option<String>)` — HTTP fetch → write to download dir, return `{ path, bytes }` 15 + - [ ] `download_video(url: String, filename: Option<String>)` — fetch m3u8 manifest, resolve best variant, download TS segments, concatenate to MP4, return `{ path, bytes }` 16 + - [ ] Emit `download-progress` events during video download for frontend progress UI 17 + - [ ] Filename collision handling: append `_1`, `_2`, etc. if file already exists 18 + - [ ] Add `dialog:default` and scoped `fs` permissions to `capabilities/default.json` 19 + 20 + ### Frontend - Video Player (`src/components/feeds/VideoEmbed.tsx`) 21 + 22 + - [ ] `VideoEmbed` component: `<video>` element with poster from `thumbnail`, native controls 23 + - [ ] Lazy-load HLS.js — attach to video element only when `playlist` URL is m3u8 24 + - [ ] Click-to-play: show thumbnail + centered play button overlay, start playback on click 25 + - [ ] Respect `aspectRatio` from embed to prevent layout shift 26 + - [ ] Render `alt` text as caption below player when present 27 + - [ ] Replace `ExternalEmbed` fallback in `EmbedContent` switch for `app.bsky.embed.video#view` 28 + - [ ] Download button in player controls area → invoke `download_video` command 29 + 30 + ### Frontend - Image Gallery (`src/components/feeds/ImageGallery.tsx`) 31 + 32 + - [ ] Gallery overlay: glass background (`surface_container_highest` 70% + backdrop-blur 20px) 33 + - [ ] Display `fullsize` image with `object-contain`, constrained to viewport 34 + - [ ] `Presence` fade-in/fade-out transitions 35 + - [ ] Left/right navigation arrows + position indicator for multi-image posts 36 + - [ ] Keyboard: `Escape` close, `ArrowLeft`/`ArrowRight` navigate 37 + - [ ] Caption panel: alt text (`body-md`), post text truncated to 2 lines with expand, author handle as link 38 + - [ ] Download button in gallery toolbar → invoke `download_image` command 39 + - [ ] Wire `ImageEmbed` click handler to open gallery at the clicked image index 40 + 41 + ### Frontend - Download UX 42 + 43 + - [ ] Download button spinner/progress indicator while active 44 + - [ ] Success toast: filename + "Open in Finder" action (via `tauri-plugin-opener`) 45 + - [ ] Error toast: human-readable failure message 46 + - [ ] Right-click context menu on inline images with "Save image" option 47 + 48 + ### Frontend - Settings Integration 49 + 50 + - [ ] Add "Downloads" section to Settings view between "Data" and "Danger Zone" 51 + - [ ] Path display + "Browse" button using Tauri `dialog.open({ directory: true })` 52 + - [ ] "Reset to default" link to restore `~/Downloads` 53 + 54 + ### Parking Lot 55 + 56 + - [ ] Custom video player controls (scrubber, volume, speed) 57 + - [ ] Pinch-to-zoom and swipe gestures in gallery 58 + - [ ] Download queue with concurrent downloads 59 + - [ ] Batch download (all images in a post) 60 + - [ ] Save to custom album/folder per account