What do you want to do with Hls.js?
Hello
First of all I want to thank everybody for contributing to hls.js. Good job!
I want to know, if it is possible to fetch the .ts files parallel instead of sequentiell. I saw you can override the loader implementation, but I'm not sure where to start. So I would appreciate any advice or experience.
Pls help me and thank you.
What have you tried so far?
I believe it is not possible without changing the internal representation of hls.js, as the internal loader allows you to change what happens when a request is made, but does not allow you to change when a request is made.
Curious, why do you want to fetch the .ts files in parallel?
I was able to solve this by implementing my own loader class (StreamLoader) which is downloading up to 5 fragments parallel. The StreamLoader is creating the index.m3u8 with blob urls instead of the .ts files urls and handing them over to the pLoader (onSuccess).
I want to fetch the files parallel, because our requirement is to provide a low latency stream (<3sec). Consequently our chunk size is 0.2s but the CDN we are using couldn't deliver the files within the 200ms constantly.
@silltho could you maybe publish your StreamLoader or a sample implementation? In our use case the playback latency is not that important, but rather having stable playback that survives congested wifi networks for congested network peerings is important, and a StreamLoader as described by you should greatly improve that while not increasing load on the server side.
@basisbit I can provide the LoaderClass that we were using i hope this gives you an idea how this setup works. We are not using this solution for quite some time so I can't show you how we used this class to override the hls.js Loader property. What i can tell you is that it took quite some time to understand and implement all the parts that are happening while fetching fragments like abr, and buffering, memory usage and so on, but at the end we had a decent solution that worked. However we needed also some backend services like a socket-connection to inform the loader that new fragments are available.
At the end we used it for a little while but saw that it's difficult to maintain that custom code, so we are waiting for hls.js to realease v1.0.0 and provide a more standardised solution.
Hope this helps you. Please let me know if you achieved something, we still think that fetching fragments parallel would be a nice feature.
import { HlsFragment, PendingFragmentRequests, RequestStats } from '../../types'
function pad(num: number, size: number): string {
let s = String(num)
while (s.length < (size || 2)) {
s = '0' + s
}
return s
}
const maxRequestsCount = 3
export default class {
private pendingRequests: PendingFragmentRequests = {}
private lastLoadedFragment!: number
private baseUrl: string
private encodingKey: string
private abortController = new AbortController()
public constructor(baseUrl: string, encodingKey: string) {
this.baseUrl = baseUrl
this.encodingKey = encodingKey
}
public setLastLoadedFragment = (lastLoadedFragment: number) => {
this.lastLoadedFragment = lastLoadedFragment
}
public setEncodingKey = (encodingKey: string) => {
this.encodingKey = encodingKey
}
public setBaseUrl = (baseUrl: string) => {
this.baseUrl = baseUrl
}
public loadFragments = (latestIndex: number) => {
let nextFragmentKey = this.findNextFragment()
const newFragmentRequests: Promise<HlsFragment>[] = []
while (
Object.keys(this.pendingRequests).length < maxRequestsCount &&
nextFragmentKey < latestIndex
) {
const request = this.loadFragment(nextFragmentKey).then(
(fragment: HlsFragment) => {
if (fragment.id > this.lastLoadedFragment) {
// track the highest loaded fragment to prevent fetching the same multiple times
this.lastLoadedFragment = fragment.id
}
delete this.pendingRequests[fragment.id]
return fragment
}
)
this.pendingRequests[nextFragmentKey] = request
newFragmentRequests.push(request)
nextFragmentKey = this.findNextFragment()
}
return Promise.all(newFragmentRequests)
}
private getFragmentUrl = (fragmentId: number) => {
return `${this.baseUrl}/${this.encodingKey}/${pad(fragmentId, 20)}.ts`
}
private loadFragment = (fragmentId: number): Promise<HlsFragment> => {
const url = this.getFragmentUrl(fragmentId)
const stats: RequestStats = {
trequest: performance.now(),
tfirst: 0,
tload: 0,
}
return fetch(url, { signal: this.abortController.signal })
.then((res) => {
stats.tfirst = performance.now() - 10 // remove 10ms to get a more accurate download duration
return res.blob()
})
.then((data) => {
stats.tload = performance.now()
return {
id: fragmentId,
blob: data,
url: URL.createObjectURL(data),
stats: stats,
encodingKey: this.encodingKey,
}
})
}
private findNextFragment = (): number => {
const lastPending = this.getLastPendingFragment()
if (lastPending && lastPending > this.lastLoadedFragment) {
return lastPending + 1
}
return this.lastLoadedFragment + 1
}
private getLastPendingFragment = () => {
const pendingFragmentKeys = Object.keys(this.pendingRequests)
return parseInt(pendingFragmentKeys[pendingFragmentKeys.length - 1], 10)
}
}