Skip to main content
The popup is the main user interface for HLS Downloader. It provides a 500x600px React application that allows users to browse detected playlists, select quality levels, and manage downloads.

Package information

Package
string
@hls-downloader/popup
Location
string
src/popup/src/
Entry point
string
src/popup/src/index.tsx

Application structure

App component

Root application component that sets up the theme and router. Location: src/popup/src/App.tsx:5
import React from "react";
import RouterModule from "./modules/Navbar/RouterModule";
import { useTheme } from "@hls-downloader/design-system";

function App() {
  useTheme();
  return (
    <div
      id="hls-downloader-ext"
      className="w-[500px] h-[600px] transition-all pt-4 font-sans antialiased"
    >
      <RouterModule></RouterModule>
    </div>
  );
}
The popup has fixed dimensions of 500x600 pixels and uses the design system’s theme hook.

Modules

The popup is organized into feature modules located in src/popup/src/modules/.

Sniffer module

Location: src/popup/src/modules/Sniffer/ Displays detected HLS playlists from the current browsing session.

SnifferView props

Location: src/popup/src/modules/Sniffer/SnifferView.tsx:39
playlists
Playlist[]
Array of detected playlists
currentPlaylistId
string | undefined
ID of the currently selected playlist
currentPlaylist
Playlist | null
Full playlist object for the selected playlist
currentPlaylistStatus
PlaylistStatus | null
Loading/ready status of the current playlist
filter
string
Filter string for playlist search
clearPlaylists
() => void
Callback to clear all detected playlists
copyPlaylistsToClipboard
() => void
Callback to copy playlist URLs to clipboard
setFilter
(filter: string) => void
Update the filter string
setCurrentPlaylistId
(playlistId?: string) => void
Select a playlist by ID
directURI
string
Manual playlist URL input
setDirectURI
(uri: string) => void
Update the direct URI input
addDirectPlaylist
() => void
Add a playlist from the direct URI input
expandedPlaylists
string[]
Array of expanded playlist IDs
toggleExpandedPlaylist
(id: string) => void
Toggle playlist expansion state

Key features

Direct URL input

Users can manually paste m3u8 URLs to add playlists

Playlist filtering

Search/filter detected playlists by title or URL

Animated transitions

GSAP-powered animations for smooth list/detail transitions

Expandable rows

Accordion-style playlist rows with details
Example: Playlist row structure
<Card className="mb-2 w-full">
  <button onClick={onToggle}>
    <ChevronDown />
    <div>{playlist.pageTitle}</div>
    <div>{new Date(playlist.createdAt).toLocaleTimeString()}</div>
  </button>
  <div className="details">
    <Button onClick={onOpen}>Open</Button>
    <Button onClick={copyUrl}>Copy URL</Button>
  </div>
</Card>

Playlist module

Location: src/popup/src/modules/Playlist/ Displays quality/track selection for a specific playlist.

PlaylistView props

Location: src/popup/src/modules/Playlist/PlaylistView.tsx:31
status
PlaylistStatus | null
Current status: fetching, ready, or error
videoLevels
Level[]
Available video quality levels
audioLevels
Level[]
Available audio tracks
subtitleLevels
Level[]
Available subtitle tracks
selectedVideoId
string
ID of selected video quality
selectedAudioId
string
ID of selected audio track
selectedSubtitleId
string
ID of selected subtitle track
onSelectVideo
(id: string) => void
Callback when video quality is selected
onSelectAudio
(id: string) => void
Callback when audio track is selected
onSelectSubtitle
(id: string) => void
Callback when subtitle track is selected
onDownload
() => void
Callback to start download
canDownload
boolean
Whether download button should be enabled
encryptionSummaries
array
Encryption status for each track
inspectionPending
boolean
Whether encryption inspection is in progress
encryptionBlocked
boolean
Whether unsupported encryption is blocking download

Track detail formatting

Location: src/popup/src/modules/Playlist/PlaylistView.tsx:94 The component formats track details for display:
Video details
function getVideoDetails(item: Level) {
  return [
    item.width && item.height ? `${item.width}×${item.height}` : undefined,
    item.bitrate ? `${(item.bitrate / 1024 / 1024).toFixed(1)} mbps` : undefined,
    item.fps ? `${item.fps} fps` : undefined,
  ]
    .filter(Boolean)
    .join(" · ");
}
Audio details
function getAudioDetails(item: Level) {
  return [
    item.language,
    item.name && item.name !== item.language ? item.name : undefined,
    item.channels ? `${item.channels}ch` : undefined,
    item.bitrate ? `${(item.bitrate / 1024 / 1024).toFixed(1)} mbps` : undefined,
    item.isDefault ? "default" : undefined,
    item.autoSelect ? "auto" : undefined,
  ]
    .filter(Boolean)
    .join(" · ");
}

Downloads module

Location: src/popup/src/modules/Downloads/ Displays active and completed download jobs.

DownloadsView props

Location: src/popup/src/modules/Downloads/DownloadsView.tsx:18
jobs
Job[]
Array of download jobs
hasJobs
boolean
Whether any jobs exist
showFilterInput
boolean
Whether to show the filter input
currentJobId
string | undefined
ID of the currently selected job
filter
string
Filter string for job search
setCurrentJobId
(jobId?: string) => void
Select a job by ID
setFilter
(filter: string) => void
Update the filter string

Settings module

Location: src/popup/src/modules/Settings/ Provides user configuration options.

About module

Location: src/popup/src/modules/About/ Displays version information and links.

Router module

Location: src/popup/src/modules/Navbar/ Handles navigation between modules using React Router.

State management

The popup uses Redux for state management via webext-redux, which synchronizes state with the background script.
import { Provider } from "react-redux";
import { Store } from "webext-redux";

const store = new Store();

<Provider store={store}>
  <App />
</Provider>

Animations

Library: GSAP 3.13.0 The popup uses GSAP for smooth animations, particularly in the Sniffer module for transitioning between list and detail views. Location: src/popup/src/modules/Sniffer/SnifferView.tsx:78
useLayoutEffect(() => {
  const tl = gsap.timeline();
  
  if (currentPlaylistId) {
    tl.set(listEl, { display: "none", opacity: 0 })
      .set(detailEl, { display: "block", x: 32, opacity: 0 })
      .to(detailEl, {
        x: 0,
        opacity: 1,
        duration: 0.28,
        ease: "power2.out",
      });
  }
  
  return () => tl.kill();
}, [currentPlaylistId]);

Dependencies

  • React 18.3.1 - UI library
  • React Router 6.10.0 - Navigation
  • Redux Toolkit - State management
  • webext-redux 2.1.9 - Redux bridge for extensions
  • GSAP 3.13.0 - Animation library
  • HLS.js 1.6.15 - HLS playlist parsing
  • Lucide React 0.137.0 - Icons
  • @hls-downloader/design-system - UI components
  • @hls-downloader/core - Business logic

Development

pnpm --filter ./src/popup run build
Reuse components from @hls-downloader/design-system for consistent styling. Use two-space indentation.

Build docs developers (and LLMs) love