Overview
The store manages all application state using Redux Toolkit and Redux-Observable. State is organized into slices, each responsible for a specific domain (playlists, jobs, levels, etc.). The store is configured with epic middleware for handling asynchronous operations.
Store configuration
createStore
Creates the Redux store with epic middleware and dependencies.
Service implementations (loader, parser, decryptor, fs)
Optional initial state for hydration
Returns : Store - Configured Redux store
Implementation (src/core/src/store/configure-store.ts:10)
Usage
import { configureStore , Middleware } from "@reduxjs/toolkit" ;
import { createEpicMiddleware } from "redux-observable" ;
import { createRootEpic } from "../controllers/root-epic" ;
import { Dependencies } from "../services" ;
import { rootReducer , RootState , RootAction } from "./root-reducer" ;
import logger from "redux-logger" ;
export function createStore (
dependencies : Dependencies ,
preloadedState ?: Partial < RootState >
) {
const epicMiddleware = createEpicMiddleware <
RootAction ,
RootAction ,
RootState
> ({ dependencies });
const rootEpic = createRootEpic ();
const store = configureStore < RootState , RootAction , Middleware []>({
reducer: rootReducer ,
middleware: [ logger , epicMiddleware ],
preloadedState ,
}))
epicMiddleware . run ( rootEpic );
store . dispatch ({
type: "init/start" ,
});
return store ;
}
export type Store = ReturnType < typeof createStore >;
The store automatically dispatches an init/start action after creation, which triggers the initialization epic.
Root reducer
The root reducer combines all slice reducers into a single state tree.
Implementation (src/core/src/store/root-reducer.ts:15)
State shape
import { combineReducers } from "@reduxjs/toolkit" ;
import {
levelsSlice ,
playlistsSlice ,
configSlice ,
tabsSlice ,
jobsSlice ,
} from "./slices" ;
import { levelInspectionsSlice } from "./slices/level-inspections-slice" ;
import { playlistPreferencesSlice } from "./slices/playlist-preferences-slice" ;
import { subtitlesSlice } from "./slices/subtitles-slice" ;
export const rootReducer = combineReducers ({
playlists: playlistsSlice . reducer ,
levels: levelsSlice . reducer ,
config: configSlice . reducer ,
tabs: tabsSlice . reducer ,
jobs: jobsSlice . reducer ,
subtitles: subtitlesSlice . reducer ,
levelInspections: levelInspectionsSlice . reducer ,
playlistPreferences: playlistPreferencesSlice . reducer ,
});
export type RootState = ReturnType < typeof rootReducer >;
State slices
Jobs slice
Manages download jobs and their status.
State (src/core/src/store/slices/jobs-slice.ts:9)
Actions
Usage
export interface IJobsState {
jobs : Record < string , Job | null >;
jobsStatus : Record < string , JobStatus | null >;
}
Job status states :
queued: Job is waiting to start
downloading: Fragments are being downloaded
ready: All fragments downloaded, ready to save
saving: File is being saved to disk
done: Job completed successfully
error: Job failed with error
Playlists slice
Manages HLS master playlists and their fetch status.
State (src/core/src/store/slices/playlists-slice.ts:23)
Actions
Usage
export interface IPlaylistsState {
playlistsStatus : Record < string , PlaylistStatus | null >;
playlists : Record < string , Playlist | null >;
}
Levels slice
Manages quality levels and tracks (video, audio, subtitle) from playlists.
export interface ILevelsState {
levels : Record < string , Level | null >;
}
Config slice
Manages application configuration and user preferences.
State (src/core/src/store/slices/config-slice.ts:27)
Initial state (src/core/src/store/slices/config-slice.ts:59)
Actions
Usage
export interface IConfigState {
concurrency : number ;
saveDialog : boolean ;
fetchAttempts : number ;
preferredAudioLanguage : string | null ;
maxActiveDownloads : number ;
}
Configuration options :
Number of fragments to download simultaneously
Whether to show save dialog when downloading
Maximum retry attempts for failed network requests
preferredAudioLanguage
string | null
default: "null"
Preferred audio language code (e.g., ‘en’, ‘es’)
Maximum number of concurrent download jobs (0 = unlimited)
Tabs slice
Manages browser tab information for tracking playlist sources.
export interface ITabsState {
tabs : Record < number , Tab | null >;
}
Subtitles slice
Manages downloaded subtitle content before saving.
subtitlesSlice . actions = {
store : ( payload : { levelId : string ; text : string }) => Action ,
clear : () => Action ,
}
Level inspections slice
Manages encryption inspection results for quality levels.
levelInspectionsSlice . actions = {
inspect : ( payload : { levelId : string }) => Action ,
inspectSuccess : ( payload : { inspection : Inspection }) => Action ,
inspectFailed : ( payload : { levelId : string ; message : string }) => Action ,
}
Playlist preferences slice
Manages user preferences for specific playlists.
playlistPreferencesSlice . actions = {
setPreference : ( payload : { playlistId : string ; preference : Preference }) => Action ,
}
Action types
All actions are typed using typesafe-actions:
Root action (src/core/src/store/root-reducer.ts:27)
export type RootAction =
| EmptyAction < "init/start" >
| EmptyAction < "init/done" >
| ActionType < typeof jobsSlice . actions >
| ActionType < typeof tabsSlice . actions >
| ActionType < typeof configSlice . actions >
| ActionType < typeof levelsSlice . actions >
| ActionType < typeof playlistsSlice . actions >
| ActionType < typeof subtitlesSlice . actions >
| ActionType < typeof levelInspectionsSlice . actions >
| ActionType < typeof playlistPreferencesSlice . actions >;
Selectors
Access state using selectors for type safety:
// Get all jobs
const jobs = store . getState (). jobs . jobs ;
// Get specific job
const job = store . getState (). jobs . jobs [ 'job-123' ];
// Get job status
const status = store . getState (). jobs . jobsStatus [ 'job-123' ];
// Get all playlists
const playlists = store . getState (). playlists . playlists ;
// Get levels for a playlist
const levels = Object . values ( store . getState (). levels . levels )
. filter ( level => level ?. playlistID === 'playlist-1' );
// Get config
const config = store . getState (). config ;
Persistence
The store does not automatically persist state. To persist state across sessions:
// Save state
const state = store . getState ();
localStorage . setItem ( 'hls-downloader-state' , JSON . stringify ( state ));
// Load state
const savedState = JSON . parse ( localStorage . getItem ( 'hls-downloader-state' ));
const store = createStore ( dependencies , savedState );
Middleware
The store uses these middleware:
redux-logger : Logs all actions and state changes (development)
epicMiddleware : Runs Redux-Observable epics for async operations
TypeScript types
Export types for use in consuming code:
import type {
RootState ,
RootAction ,
Store
} from '@hls-downloader/core' ;
function useJobs ( store : Store ) {
const state : RootState = store . getState ();
return state . jobs ;
}