-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor fragment loading to be promise-based & Typescript loaders #2123
Conversation
Refactor classes which load fragments in order to utilize new promise flow
I went to go test it with the promise loading to see if it resolved some of the issues from #2099, and the initSegment loading cycles super fast cancelling itself. |
Yeah that is weird, I’ll take a look tomorrow
…On Sat, Feb 9, 2019 at 19:41 Jamie Stackhouse ***@***.***> wrote:
Weird behaviour with
https://deploy-preview-2123--hls-js-pr.netlify.com/demo/?src=https%3A%2F%2Fdemo.unified-streaming.com%2Fvideo%2Ftears-of-steel%2Ftears-of-steel-fmp4.ism%2F.m3u8&demoConfig=eyJlbmFibGVTdHJlYW1pbmciOnRydWUsImF1dG9SZWNvdmVyRXJyb3IiOnRydWUsImR1bXBmTVA0IjpmYWxzZSwibGV2ZWxDYXBwaW5nIjotMSwibGltaXRNZXRyaWNzIjotMX0=
I went to go test it with the promise loading to see if it resolved some
of the issues, and the initSegment loading cycles super fast cancelling
itself.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#2123 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AEzwZCGT7NZdg0NqMGVL0iBfMXTeaClUks5vL2q8gaJpZM4aywzm>
.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 on the typescript.
this.config = config; | ||
} | ||
|
||
load (frag: Fragment): Promise<FragLoadSuccessResult | FragLoadFailResult> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There doesn't seem to be a method to define the type for the rejection case of a promise. I'm not sure what the best experience here would be, going to look at other public facing APIs for TypeScript.
What were you're experiences with typing load
this way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tested this in another TS file, the controllers are still JS. I added this for TS completeness but I can remove it for now if it's too much of a hassle
src/loader/fragment-loader.ts
Outdated
|
||
return new Promise((resolve, reject) => { | ||
if (!loader) { | ||
return Promise.reject(new LoadError(null, 'Loader was destroyed after fragment request')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning a new rejected promise, I think calling the reject callback here, or throwing would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right meant to do that
src/types/loader.ts
Outdated
aborted: boolean | ||
} | ||
|
||
export interface LoaderCallbacks { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is some typing code that I had locally when working on the playlist loader. The only real change is that it makes the Loader interface able to take concrete implementations of the loading context, and that the callbacks are typed because of that.
This will allow us to share the same Loader interface between Frag and Playlist loaders without having a bunch of optional properties. For the Typescript side, it uses the generics part of the language to enable this.
export interface LoaderContext {
// target URL
url: string
// loader response type (arraybuffer or default response type for playlist)
responseType: XMLHttpRequestResponseType
// start byte range offset
rangeStart?: number
// end byte range offset
rangeEnd?: number
// true if onProgress should report partial chunk of loaded content
progressData?: boolean
}
export interface LoaderResponse {
url: string,
data: string | ArrayBuffer
}
export interface LoaderConfiguration {
// Max number of load retries
maxRetry: number
// Timeout after which `onTimeOut` callback will be triggered
// (if loading is still not finished after that delay)
timeout: number
// Delay between an I/O error and following connection retry (ms).
// This to avoid spamming the server
retryDelay: number
// max connection retry delay (ms)
maxRetryDelay: number
}
type LoaderOnSuccess < T extends LoaderContext > = (
response: LoaderResponse,
stats: LoaderStats,
context: T,
networkDetails: any
) => void;
type LoaderOnProgress < T extends LoaderContext > = (
stats: LoaderStats,
context: T,
data: string | ArrayBuffer,
networkDetails: any,
) => void;
type LoaderOnError < T extends LoaderContext > = (
error: {
// error status code
code: number,
// error description
text: string,
},
context: T,
networkDetails: any,
) => void;
type LoaderOnTimeout < T extends LoaderContext > = (
stats: LoaderStats,
context: T,
) => void;
export interface LoaderCallbacks<T extends LoaderContext>{
onSuccess: LoaderOnSuccess<T>,
onError: LoaderOnError<T>,
onTimeout: LoaderOnTimeout<T>,
onProgress?: LoaderOnProgress<T>,
}
export interface Loader<T extends LoaderContext> {
destory(): void
abort(): void
load(
context: LoaderContext,
config: LoaderConfiguration,
callbacks: LoaderCallbacks<T>,
): void
context: T
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice I'll add these in
…or compatibility in base-stream-controller
I changed the base branch from master to @itsjamie I noticed some weird results running tests locally with Istanbul enabled. Have you noticed that it's not always running all tests? On one run it'll say something like |
Yeah, I've noticed Istanbul doing weird things reporting different test numbers. |
@itsjamie Addressed feedback & integrated your types from the playlist-loader TS pr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
✅ All demo page assets work as expected (with the exception of a couple streams which are unavailable)
✅ Can't reproduce the issue spotted by @itsjamie when checking out this branch locally and testing. But I can see it on the deploy preview (updates to this branch might not have been propagated to netlify?)
@johnBartos @itsjamie
I think it would be good to get this in the 1.0.0
branch soon as it's a pre-req for LHLS
@@ -124,7 +124,7 @@ export default class Hls extends Observer { | |||
const capLevelController = new config.capLevelController(this); | |||
const fpsController = new config.fpsController(this); | |||
const playListLoader = new PlaylistLoader(this); | |||
const fragmentLoader = new FragmentLoader(this); | |||
// const fragmentLoader = new FragmentLoader(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if you wanna remove or keep this @johnBartos ?
No longer valid, I've made a bunch of changes since opening this PR. Would probably be easier to bundle this with all of the other refactoring work I've done for 1.0.0 |
This PR will...
fragment-loader.js
:load
now takes a fragment and returns a promiseFRAG_LOADED
event, and returnsonSuccess
ERROR
, and returnsonError,
andonTimeout
loadprogress
(for now)fragCurrent
by reference)*-stream-controller.js
to use new promise based data flowfragment-loader
instancefragmentLoader.load
FRAG_LOADING
for API compatibilityFRAG_LOADED
ERROR
events on load error_loadFragForPlayback
:onFragLoaded
functionFRAG_LOADED
event for API compatibility_loadBitrateTestFrag
FRAG_LOADED
_loadInitSegment
FRAG_LOADED
fragment-loader
andxhr-loader
This is a big one but each task is in its own commit:
The only catch: Hls.js now relies on Promises to be present. This is probably API breaking. We can merge this into a
1.0.0
branch if we decide not to ship this in a minor.Why is this Pull Request needed?
Making fragment loading private and explicit to the controller which requested it allows us to simplify load callbacks. Instead of having each
FRAG_LOADED
callback check whether it should handle the event (by checking fragment type & controller state), promises allow each controller to handle only the fragment it requested. This allows us to remove a few checks at the top of each callback. Furthermore, we can split out specialized fragment loading scenarios (bitrate test, init segment) which are not demuxed into their own functions by attaching a different.then
.In addition to code cleanliness, this new pattern is the first step in enabling LHLS. LHLS requires progressive playback, which means that we're not going to have the same event flow that we have now; a fragment will be loading, parsing, and buffering at the same time, several times during its download. Our current architecture cannot handle this because it relies on events being fired in a specific sequence to propagate fragment data. By shifting to promises we eliminate the dependency on events for propagating fragment data.
Are there any points in the code the reviewer needs to double check?
Resolves issues:
N/A
Checklist