Skip to main content
IINA Plugin BookmarksIINA Plugin Bookmarks
Development

Architecture

Technical architecture and design patterns of the IINA Plugin Bookmarks

Architecture

This document summarizes the current runtime architecture in src/ and the UI messaging contract used by the plugin.

Project Structure

iina-plugin-bookmarks/
├── src/
│   ├── index.ts                 # Plugin entry point
│   ├── bookmark-manager.ts      # Core orchestrator for bookmark + UI workflows
│   ├── bookmark-import-export.ts # Import/export parser + serializers
│   ├── bookmark-persistence.ts  # Preferences storage + backup logic
│   ├── thumbnail-generator.ts   # FFmpeg thumbnail generation helper
│   ├── types.ts                 # Shared runtime types + message payload maps
│   └── utils/
│       ├── formatTime.ts        # Time formatting helpers
│       └── validation.ts        # Input validation/sanitization helpers
├── ui/
│   ├── sidebar/                 # Sidebar React app
│   ├── overlay/                 # Overlay React app
│   ├── window/                  # Standalone window React app
│   ├── components/              # Shared UI components
│   ├── hooks/                   # Shared hooks
│   └── types.ts                 # UI-side types
├── tests/                       # Vitest test suites
└── docs/                        # Fumadocs site

Core Components

BookmarkManager

BookmarkManager is the central coordinator for bookmarks, collections, playback integration, and UI messaging.

class BookmarkManager {
  private bookmarks: BookmarkData[];
  private collections: BookmarkCollection[];
  private smartCollections: SmartCollection[];
  private persistence: BookmarkPersistence;
  private importExport: BookmarkImportExport;
  private thumbnailGenerator: ThumbnailGenerator;

  constructor(dependencies: IINARuntimeDependencies)

  async addBookmark(...): Promise<string | undefined>
  removeBookmark(id: string): void
  updateBookmark(id: string, data: BookmarkUpdatableFields): void
  jumpToBookmark(id: string): void

  createCollection(name: string, ...): void
  createSmartCollection(name: string, ...): void
  batchDelete(ids: string[]): void
  setABLoop(bookmarkId: string): void
  clearABLoop(): void
}

BookmarkPersistence

Persists bookmarks in preferences, keeps a shadow backup, and can recover after parse/storage failures.

class BookmarkPersistence {
  load(): BookmarkData[];
  save(bookmarks: BookmarkData[]): void;
  saveAutoBackup(bookmarks: BookmarkData[]): void;
  recover(): BookmarkData[] | null;
}

BookmarkImportExport

Validates import input, enforces caps, and supports v1/v2 JSON export formats plus CSV export.

class BookmarkImportExport {
  import(
    existing: BookmarkData[],
    raw: unknown[],
    options?: ImportOptions,
  ): {
    bookmarks: BookmarkData[];
    result: ImportResult;
  };
  exportJSON(bookmarks: BookmarkData[]): string;
  exportJSONv2(
    bookmarks: BookmarkData[],
    collections: BookmarkCollection[],
    smartCollections: SmartCollection[],
  ): string;
  exportCSV(bookmarks: BookmarkData[]): string;
}

ThumbnailGenerator

Generates bookmark thumbnails on demand via ffmpeg when iina.utils.exec is available.

class ThumbnailGenerator {
  generate(bookmark: BookmarkData): string | null;
}

Design Patterns

Dependency Injection

The plugin is initialized with IINARuntimeDependencies from src/types.ts:

interface IINARuntimeDependencies {
  console: IINAConsole;
  core: IINACore;
  preferences: IINAPreferences;
  menu: IINAMenu;
  event: IINAEvent;
  sidebar: IINASidebar;
  overlay: IINAOverlay;
  standaloneWindow: IINAStandaloneWindow;
  utils: IINAUtils;
  file: IINAFile;
  mpv?: IINAMpv;
  playlist?: IINAPlaylist;
}

Message-Based UI Communication

Backend and UI communicate through named messages with typed payload maps in src/types.ts.

// UI -> Backend (examples)
ADD_BOOKMARK: { title?: string; timestamp?: number; description?: string; tags?: string[]; color?: BookmarkColor; endTimestamp?: number }
UPDATE_BOOKMARK: { id: string; data: BookmarkUpdatableFields }
CREATE_COLLECTION: { name: string; description?: string; color?: BookmarkColor; icon?: string }
REQUEST_THUMBNAIL: { bookmarkId: string }
SEEK_TO_TIMESTAMP: { timestamp: number }

// Backend -> UI (examples)
BOOKMARKS_UPDATED: BookmarkData[]
COLLECTIONS_UPDATED: BookmarkCollection[]
SMART_COLLECTIONS_UPDATED: SmartCollection[]
BOOKMARK_NEAR_DUPLICATE: { existingBookmark: BookmarkData; proposedTimestamp: number; distance: number }
THUMBNAIL_READY: { bookmarkId: string; path: string }
ERROR: { message: string }

Separation of Concerns

  • BookmarkManager: orchestration + message routing + playback/menu integration
  • BookmarkPersistence: storage + recovery + backup writes
  • BookmarkImportExport: import/export transforms and validation boundaries
  • ThumbnailGenerator: FFmpeg thumbnail capture
  • Validation utils: centralized sanitization and guardrails

Data Flow

Bookmark Creation Flow

graph TD
    A[UI sends ADD_BOOKMARK] --> B[BookmarkManager.addBookmark]
    B --> C[Validate timestamp + max bookmark limit]
    C --> D{Near duplicate in current file?}
    D -->|Yes| E[Emit BOOKMARK_NEAR_DUPLICATE to UIs]
    D -->|No| F[Sanitize fields + enrich chapter/subtitle]
    F --> G[Append bookmark]
    G --> H[BookmarkPersistence.save + saveAutoBackup]
    H --> I[Refresh UI via BOOKMARKS_UPDATED]

Import Flow

graph TD
    A[UI sends IMPORT_BOOKMARKS] --> B[BookmarkImportExport.import]
    B --> C[Validate + sanitize entries]
    C --> D[Apply duplicate strategy: skip/replace/merge]
    D --> E[Persist updated bookmarks]
    E --> F[Send IMPORT_RESULT]
    E --> G[Merge collections/smart collections when present]

Storage Architecture

Bookmark Model

interface BookmarkData {
  id: string;
  title: string;
  timestamp: number;
  filepath: string;
  description?: string;
  createdAt: string;
  updatedAt: string;
  tags: string[];
  color?: BookmarkColor;
  endTimestamp?: number;
  pinned?: boolean;
  thumbnailPath?: string;
  chapterTitle?: string;
  subtitleText?: string;
  scratchpad?: boolean;
}

Collection Models

interface BookmarkCollection {
  id: string;
  name: string;
  description?: string;
  bookmarkIds: string[];
  color?: BookmarkColor;
  icon?: string;
  createdAt: string;
  updatedAt: string;
}

interface SmartCollection {
  id: string;
  name: string;
  description?: string;
  filters: SmartCollectionFilters;
  color?: BookmarkColor;
  icon?: string;
  createdAt: string;
  usageCount: number;
  builtin?: boolean;
}

Backup Behavior

  • Main data: preferences['bookmarks']
  • Shadow backup: preferences['bookmarks_backup'] before writes
  • Debounced file backup: @data/bookmarks-backup.json (best-effort)

Error Handling & Security

  • User-provided strings are sanitized with stripHtmlTags().
  • Timestamp and range fields are validated against MAX_TIMESTAMP and ordering rules.
  • CSV export prefixes dangerous leading characters to prevent formula injection.
  • Reconciliation and import paths return explicit result payloads to UI (FILE_RECONCILIATION_RESULT, IMPORT_RESULT).

Performance Notes

  • Overlay receives server-filtered bookmarks for current file only.
  • Sidebar/window receive full lists and do client-side filtering.
  • Playback status is broadcast on file load and then every 5 seconds.
  • Thumbnail generation is lazy and opt-in (REQUEST_THUMBNAIL).

Cloud sync is not part of the current src/ runtime. If cloud sync is reintroduced, document it as a roadmap item until code ships.