forked from FaisalUmair/udemy-downloader-gui
-
-
Notifications
You must be signed in to change notification settings - Fork 215
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ce3ee9e
commit 31e80ab
Showing
8 changed files
with
1,105 additions
and
765 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
const UdemyService = require("./udemy.service"); | ||
const M3U8Service = require("./m3u8.service"); | ||
|
||
module.exports = { | ||
default: UdemyService, | ||
M3U8Service, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
"use strict" | ||
|
||
class M3U8Service { | ||
|
||
/** | ||
* Creates a new instance of M3U8Playlist. | ||
* @param {string} m3u8Url - The URL of the M3U8 playlist. | ||
* @returns {M3U8Service} - The newly created M3U8Playlist instance. | ||
*/ | ||
constructor(m3u8Url) { | ||
if (!this._isValidUrl(m3u8Url)) { | ||
throw new Error('Invalid URL'); | ||
} | ||
this._m3u8Url = m3u8Url; | ||
/** @type {Array<{quality: number, resolution: string, url: string}>} */ | ||
this._playlist = []; | ||
} | ||
|
||
/** | ||
* Validates the URL. | ||
* @param {string} url - The URL to validate. | ||
* @returns {boolean} - True if the URL is valid, false otherwise. | ||
*/ | ||
_isValidUrl(url) { | ||
try { | ||
new URL(url); | ||
return true; | ||
} catch (_) { | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the content is a valid M3U8 playlist. | ||
* @param {string} content - The content to check. | ||
* @returns {boolean} - True if the content is a valid M3U8 playlist, false otherwise. | ||
*/ | ||
_isValidM3U8Content(content) { | ||
return content.startsWith('#EXTM3U'); | ||
} | ||
|
||
/** | ||
* Extracts URLs and qualities from an M3U8 playlist content. | ||
* @param {string} m3u8Content - The content of the M3U8 playlist. | ||
* @returns {Array<{quality: number, resolution: string, url: string}>} - An array of objects containing the quality, | ||
* resolution, and URL of each playlist. | ||
*/ | ||
_extractUrlsAndQualities(m3u8Content) { | ||
const lines = m3u8Content.split('\n'); | ||
const urlsAndQualities = []; | ||
|
||
let currentResolution = null; | ||
let currentQuality = null; | ||
|
||
lines.forEach(line => { | ||
if (line.startsWith('#EXT-X-STREAM-INF')) { | ||
const match = line.match(/RESOLUTION=(\d+x\d+)/); | ||
if (match) { | ||
currentResolution = match[1]; | ||
currentQuality = parseInt(match[1].split('x')[1], 10); | ||
} | ||
} else if (line.startsWith('http')) { | ||
if (currentResolution) { | ||
urlsAndQualities.push({ | ||
quality: currentQuality, | ||
resolution: currentResolution, | ||
url: line | ||
}); | ||
currentResolution = null; | ||
currentQuality = null; | ||
} | ||
} | ||
}); | ||
|
||
return urlsAndQualities; | ||
} | ||
|
||
/** | ||
* Fetches a file from the given URL. | ||
* | ||
* @param {string} url - The URL of the file to fetch. | ||
* @param {boolean} [isBinary=false] - Whether the file is binary or text. | ||
* @param {number} [maxRetries=3] - The maximum number of retries to fetch the file. | ||
* @returns {Promise<string|ArrayBuffer>} - A promise that resolves with the file content. | ||
* @throws {Error} - If the file fails to load after multiple attempts. | ||
*/ | ||
static async getFile(url, isBinary = false, maxRetries = 3) { | ||
let retries = 0; | ||
|
||
while (retries < maxRetries) { | ||
try { | ||
const response = await fetch(url); | ||
|
||
if (!response.ok) { | ||
throw new Error(`Failed to fetch ${isBinary ? 'binary' : 'text'} file: ${response.statusText}`); | ||
} | ||
|
||
return isBinary ? await response.arrayBuffer() : await response.text(); | ||
} catch (error) { | ||
retries++; | ||
} | ||
} | ||
|
||
throw new Error('Failed to load file after multiple attempts'); | ||
} | ||
|
||
/** | ||
* Loads the M3U8 playlist. | ||
* | ||
* @param {number} [maxRetries=3] - The maximum number of retries to fetch the playlist. | ||
* @returns {Promise<void>} - A promise that resolves when the playlist is loaded successfully. | ||
* @throws {Error} - If the playlist fails to load after multiple attempts. | ||
*/ | ||
async loadPlaylist(maxRetries = 3) { | ||
try { | ||
const playlistContent = await M3U8Service.getFile(this._m3u8Url, false, maxRetries); | ||
if (!this._isValidM3U8Content(playlistContent)) { | ||
throw new Error('Invalid M3U8 playlist content'); | ||
} | ||
this._playlist = this._extractUrlsAndQualities(playlistContent); | ||
} catch (error) { | ||
throw error; | ||
} | ||
} | ||
|
||
/** | ||
* Retrieves the playlist. | ||
* | ||
* @return {Array<{quality: number, resolution: string, url: string}>} The playlist. | ||
*/ | ||
getPlaylist() { | ||
return this._playlist; | ||
} | ||
|
||
_sortPlaylistByQuality(ascending = true) { | ||
return [...this._playlist].sort((a, b) => { | ||
const heightA = parseInt(a.resolution.split('x')[1], 10); | ||
const heightB = parseInt(b.resolution.split('x')[1], 10); | ||
return ascending ? heightA - heightB : heightB - heightA; | ||
}); | ||
} | ||
|
||
/** | ||
* Retrieves the highest quality item from the playlist. | ||
* | ||
* @return {Object|null} The highest quality item from the playlist, or null if the playlist is empty. | ||
*/ | ||
getHighestQuality() { | ||
if (this._playlist.length === 0) { | ||
return null; | ||
} | ||
return this._sortPlaylistByQuality(false)[0]; // Retornar o item de maior qualidade | ||
} | ||
|
||
/** | ||
* Retrieves the lowest quality item from the playlist. | ||
* | ||
* @return {Object|null} The lowest quality item from the playlist, or null if the playlist is empty. | ||
*/ | ||
getLowestQuality() { | ||
if (this._playlist.length === 0) { | ||
return null; | ||
} | ||
return this._sortPlaylistByQuality(true)[0]; // Retornar o item de menor qualidade | ||
} | ||
} | ||
|
||
module.exports = M3U8Service; |
Oops, something went wrong.