Overview
Use cases are factory functions that encapsulate specific business operations. Each factory accepts service dependencies and returns an async function that executes the operation. This pattern enables dependency injection and testability.
Factory pattern
All use cases follow this pattern:
export const useCaseFactory = (dependencies) => {
const run = async (...params): Promise<Result> => {
// Implementation
};
return run;
};
Fragment operations
downloadSingleFactory
Downloads a single media fragment from the network.
Network loader service for fetching data
Returns: (fragment: Fragment, fetchAttempts: number) => Promise<ArrayBuffer>
export const downloadSingleFactory = (loader: ILoader) => {
const run = async (
fragment: Fragment,
fetchAttempts: number
): Promise<ArrayBuffer> => {
const { data } = await fetchWithFallback(
fragment.uri,
fragment.fallbackUri,
fetchAttempts,
loader.fetchArrayBuffer
);
return data;
};
return run;
};
Parameters:
fragment: Fragment object with URI and fallback URI
fetchAttempts: Maximum number of retry attempts
decryptSingleFragmentFactory
Decrypts an encrypted media fragment using AES encryption keys.
Network loader service for fetching encryption keys
Decryption service for AES operations
Returns: (key: Key, data: ArrayBuffer, fetchAttempts: number) => Promise<ArrayBuffer>
export const decryptSingleFragmentFactory = (
loader: ILoader,
decryptor: IDecryptor
) => {
const run = async (
key: Key,
data: ArrayBuffer,
fetchAttempts: number
): Promise<ArrayBuffer> => {
if (!key.uri || !key.iv) {
return data;
}
const { data: keyArrayBuffer } = await fetchWithFallback(
key.uri,
key.fallbackUri,
fetchAttempts,
loader.fetchArrayBuffer
);
const decryptedData = await decryptor.decrypt(data, keyArrayBuffer, key.iv);
return decryptedData;
};
return run;
};
If the fragment has no encryption key, the original data is returned unchanged.
getFragmentsDetailsFactory
Parses a level playlist and extracts all fragment URIs with encryption details.
Network loader for fetching playlist text
Returns: (playlist: Level, fetchAttempts: number, options?: GetFragmentsDetailsOptions) => Promise<Fragment[]>
export interface GetFragmentsDetailsOptions {
baseUri?: string;
}
export const getFragmentsDetailsFactory = (
loader: ILoader,
parser: IParser
) => {
const run = async (
playlist: Level,
fetchAttempts: number,
options: GetFragmentsDetailsOptions = {}
): Promise<Fragment[]> => {
const baseUri = options.baseUri ?? playlist.playlistID ?? playlist.uri;
const primaryPlaylistUri = appendQueryParams(baseUri, playlist.uri);
const fallbackPlaylistUri =
primaryPlaylistUri !== playlist.uri ? playlist.uri : null;
const { uri: usedPlaylistUri, data: levelPlaylistText } =
await fetchWithFallback(
primaryPlaylistUri,
fallbackPlaylistUri,
fetchAttempts,
loader.fetchText
);
const fragments = parser.parseLevelPlaylist(
levelPlaylistText,
usedPlaylistUri
);
return fragments.map((fragment) => {
const primaryUri = appendQueryParams(baseUri, fragment.uri);
const fallbackUri = primaryUri !== fragment.uri ? fragment.uri : null;
const keyPrimaryUri = fragment.key.uri
? appendQueryParams(baseUri, fragment.key.uri)
: fragment.key.uri;
const keyFallbackUri =
fragment.key.uri && keyPrimaryUri !== fragment.key.uri
? fragment.key.uri
: null;
return new Fragment(
new Key(keyPrimaryUri, fragment.key.iv, keyFallbackUri),
primaryUri,
fragment.index,
fallbackUri
);
});
};
return run;
};
Playlist operations
getLevelsFactory
Fetches and parses a master playlist to extract available quality levels and tracks.
Network loader for fetching playlist
Returns: (masterPlaylistURI: string, fetchAttempts: number) => Promise<Level[]>
export const getLevelsFactory = (loader: ILoader, parser: IParser) => {
const run = async (
masterPlaylistURI: string,
fetchAttempts: number
): Promise<Level[]> => {
try {
const masterPlaylistText = await loader.fetchText(
masterPlaylistURI,
fetchAttempts
);
return parser.parseMasterPlaylist(masterPlaylistText, masterPlaylistURI);
} catch (error) {
throw Error("LevelManifest");
}
};
return run;
};
Bucket operations
Buckets are temporary storage containers for downloaded fragments before they’re merged.
createBucketFactory
Creates a new bucket to store video and audio fragments.
Returns: (bucketID: string, videoLength: number, audioLength: number) => Promise<void>
export const createBucketFactory = (fs: IFS) => {
const run = async (
bucketID: string,
videoLength: number,
audioLength: number
): Promise<void> => {
await fs.createBucket(bucketID, videoLength, audioLength);
};
return run;
};
writeToBucketFactory
Writes a downloaded and decrypted fragment to a bucket at a specific index.
Returns: (bucketID: string, index: number, data: ArrayBuffer) => Promise<void>
export const writeToBucketFactory = (fs: IFS) => {
const run = async (
bucketID: string,
index: number,
data: ArrayBuffer
): Promise<void> => {
const bucket = await fs.getBucket(bucketID);
await bucket.write(index, data);
};
return run;
};
Subtitle operations
downloadSubtitleTrackFactory
Downloads a complete subtitle track and saves it as a WebVTT file.
Network loader for fetching subtitle data
File system service for saving files
Returns: (level: Level, playlist: Playlist, fetchAttempts: number, dialog: boolean, options?: { baseUri?: string }) => Promise<string>
export const downloadSubtitleTrackFactory = (
loader: ILoader,
parser: IParser,
fs: IFS
) => {
const run = async (
level: Level,
playlist: Playlist,
fetchAttempts: number,
dialog: boolean,
options: { baseUri?: string } = {}
): Promise<string> => {
const baseUri = options.baseUri ?? playlist.uri;
const fragments = await getFragmentsDetailsFactory(loader, parser)(
level,
fetchAttempts,
{ baseUri }
);
const hasFragments = fragments.length > 0;
const textParts: string[] = [];
if (hasFragments) {
for (const fragment of fragments) {
const { data: fragmentText } = await fetchWithFallback(
fragment.uri,
fragment.fallbackUri,
fetchAttempts,
loader.fetchText
);
textParts.push(fragmentText.trim());
}
} else {
const levelUri = appendQueryParams(baseUri, level.uri);
const { data: subtitleText } = await fetchWithFallback(
levelUri,
level.uri,
fetchAttempts,
loader.fetchText
);
textParts.push(subtitleText.trim());
}
const fileName = generateSubtitleFileName()(playlist, level);
const link = URL.createObjectURL(
new Blob([textParts.join("\n\n")], { type: "text/vtt" })
);
try {
await fs.saveAs(fileName, link, { dialog });
} finally {
URL.revokeObjectURL(link);
}
return fileName;
};
return run;
};
Subtitles can be either fragmented (multiple VTT segments) or single-file. This use case handles both formats automatically.
Complete use case list
The core package exports these use cases:
createBucketFactory: Create fragment storage bucket
decryptSingleFragmentFactory: Decrypt encrypted fragment
downloadSingleFactory: Download single fragment
getFragmentsDetailsFactory: Parse playlist for fragments
getLevelsFactory: Parse master playlist for levels
getLinkBucketFactory: Get download link from bucket
writeToBucketFactory: Write fragment to bucket
writeToFileFactory: Write data to file
generateFileNameFactory: Generate video filename
generateSubtitleFileNameFactory: Generate subtitle filename
deleteBucketFactory: Delete fragment bucket
fsCleanupFactory: Clean up file system
downloadSubtitleTrackFactory: Download subtitle track
getSubtitleTextFactory: Get subtitle text content
storeSubtitleTextFactory: Store subtitle in state
inspectLevelEncryptionFactory: Inspect encryption details