Skip to content
This repository has been archived by the owner on Jan 12, 2019. It is now read-only.

Make SegmentLoader great again #1044

Merged
merged 12 commits into from
Mar 15, 2017
Merged

Make SegmentLoader great again #1044

merged 12 commits into from
Mar 15, 2017

Conversation

imbcmdth
Copy link
Member

@imbcmdth imbcmdth commented Mar 8, 2017

Description

Segment XHR-related state is strewn across large parts of SegmentLoader. The actual problem of fetching a segment and one or two other requests that the segment might depend on is fairly self-contained. This PR aims to refactor the xhr code almost completely out of SegmentLoader so that it can focus on deciding what to get, not how to get them.

Specific Changes proposed

  1. All code related to xhr is completely removed from SegmentLoader into mediaSegmentRequest - now the only thing it has to do is handle a nicely packaged response that has either failed or is ready for transmuxing
  2. All decryption code has also moved to mediaSegmentRequest including the callback management that was in MasterPlaylistController
    • The only thing MPC does with the decrypter is life-cycle management
  3. Added better tracking for mediaRequests* stats
    • mediaRequests now properly tracks the count of ALL media requests even failed or aborted requests
    • mediaRequestsErrored has the number of requests that resulted in a failure (40x, 50x, etc.)
    • mediaRequestsTimedout has the number of requests that timed-out
    • mediaRequestsAborted has the number of requests explicitly aborted by the SegmentLoader
  4. Added mediaSegmentRequest unit-tests

Requirements Checklist

  • Feature implemented / Bug fixed
  • If necessary, more likely in a feature request than a bug fix
    • Unit Tests updated or fixed
  • Reviewed by Two Core Contributors


if (progressEvent.lengthComputable) {
stats.bytesReceived = progressEvent.loaded;
stats.bandwidth = (stats.bytesReceived / roundTripTime) * 8 * 1000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should use stats.roundTripTime or use Math.max when initializing roundTripTime to avoid division by 0 (I assume the use of Math.max above was to not report a round trip of 0)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

if (error) {
// If there are errors, we have to abort any outstanding requests
abortAll(activeXhrs);
errors.unshift(error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't unshifting cause the original error that caused all the aborts to be at the end of the array? You abort all the other requests, unshift the error, and then when the aborted requests get handled, they would have an error here and unshift themselves in front of the original?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be right. It looks like I was seeing sinon's mock xhr results (which are synchronous) and was confusing it with actual behavior.

const xhr = activeXhrs[xhrKey];

// only abort xhrs that haven't had a response
if (!xhr.responseTime) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it hurt to call abort on a request that has already been aborted? If so should probably add a check for that here as well. If one of the requests causes an error, the rest of the requests are aborted which in turn would cause an error on the aborted request, which would loop through all the requests again, even the aborted one

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen any issues. Aborting an already aborted xhr seems to be a no-op and doesn't trigger any sort of readystatechange.


segment.map.bytes = new Uint8Array(request.response);

if (!Array.isArray(segment.bandwidth)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bandwidth array isn't used anywhere. What is it for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed...

* Handle all error conditions in one place and return an object
* with all the information
*
* @param {Anything} error - if non-null signals an error occured with the XHR
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think {Object|null} or {Error|null} would be acceptable here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
};
};

const handleProgress = (segment, callback) => (event) => {
/**
* Simple progress event callback handler that gathers some states before
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gathers some states stats

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@imbcmdth imbcmdth force-pushed the cleanup-segment-xhr branch 2 times, most recently from bee0e37 to ed2de70 Compare March 9, 2017 01:40
@mjneil mjneil self-assigned this Mar 9, 2017
// the request was aborted and the SegmentLoader has already started
// another request. this can happen when the timeout for an aborted
// request triggers due to a limitation in the XHR library
// do not count this as any sort of request or we risk double-counting
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit confusing. Can you explain the limitation? (Not requesting you add more detail to the actual code comment)

@mjneil
Copy link
Contributor

mjneil commented Mar 9, 2017

I'm not sure what detail I'm missing here since Travis obviously seems to think the tests should pass, but I notice that all the mediaSegmentRequest unit tests have assert.expect(n) but only have n - 2 asserts, which would lead me to believe that the tests should fail.

EDIT: Ran the tests locally and see that is because of no unexpected logs at level "warn" and no unexpected logs at level "error": assertions run on every test

@mjneil
Copy link
Contributor

mjneil commented Mar 9, 2017

I think it would be good to have a test that makes sure doneFn isn't called until segment, key, and init segment requests have responded

@@ -249,7 +249,6 @@ QUnit.test('calculates bandwidth after downloading a segment', function(assert)
// verify stats
assert.equal(loader.mediaBytesTransferred, 10, '10 bytes');
assert.equal(loader.mediaTransferDuration, 100, '100 ms (clock above)');
assert.equal(loader.mediaRequests, 1, '1 request');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why remove this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we have this same test dozens of times and it's very redundant. I actually tried to remove more of them but lost hope.

@imbcmdth imbcmdth force-pushed the cleanup-segment-xhr branch 2 times, most recently from a14dbfd to e356bb9 Compare March 14, 2017 01:14
@@ -669,6 +644,7 @@ export default class SegmentLoader extends videojs.EventTarget {
let segment = playlist.segments[mediaIndex];

return {
requestId: 'segment-loader-' + Math.random(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to do Math.random().toString(36) like you do with decryptionId in mediaSegmentRequest.

* @param {Function} doneFn - a callback that is executed after decryption has completed
*/
const decryptSegment = (decrypter, segment, doneFn) => {
const decryptionId = 'segment-request-' + Math.random().toString(36);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use segment.requestId here instead of generating another random string?

Copy link
Contributor

@mjneil mjneil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

…vent asynchronously to updating the updating property on a SourceBuffer
Copy link
Contributor

@mjneil mjneil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM PART 2

@imbcmdth imbcmdth merged commit cd7cd9b into master Mar 15, 2017
@imbcmdth imbcmdth deleted the cleanup-segment-xhr branch March 15, 2017 22:39
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants