Skip to content
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

feat: streaming events and errors #1508

Merged
merged 14 commits into from
May 16, 2024
576 changes: 289 additions & 287 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"video.js": "^7 || ^8"
},
"peerDependencies": {
"video.js": "^8.11.0"
"video.js": "^8.14.0"
},
"devDependencies": {
"@babel/cli": "^7.21.0",
Expand All @@ -93,7 +93,7 @@
"videojs-generate-karma-config": "^8.0.1",
"videojs-generate-rollup-config": "^7.0.0",
"videojs-generator-verify": "~3.0.1",
"videojs-standard": "^9.0.0",
"videojs-standard": "^9.1.0",
"water-plant-uml": "^2.0.2"
},
"generator-videojs-plugin": {
Expand Down
30 changes: 29 additions & 1 deletion src/content-steering-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,13 @@
this.dispose();
return;
}
const metadata = {
contentSteeringInfo: {
uri
}
};

this.trigger({ type: 'contentsteeringloadstart', metadata });
this.request_ = this.xhr_({
uri,
requestType: 'content-steering-manifest'
Expand Down Expand Up @@ -205,9 +211,31 @@
this.startTTLTimeout_();
return;
}
const steeringManifestJson = JSON.parse(this.request_.responseText);
this.trigger({ type: 'contentsteeringloadcomplete', metadata });
let steeringManifestJson;

try {
steeringManifestJson = JSON.parse(this.request_.responseText);
} catch (parseError) {
const errorMetadata = {

Check warning on line 220 in src/content-steering-controller.js

View check run for this annotation

Codecov / codecov/patch

src/content-steering-controller.js#L220

Added line #L220 was not covered by tests
errorType: videojs.Error.StreamingContentSteeringParserError,
error: parseError
};

this.trigger({ type: 'error', metadata: errorMetadata });

Check warning on line 225 in src/content-steering-controller.js

View check run for this annotation

Codecov / codecov/patch

src/content-steering-controller.js#L225

Added line #L225 was not covered by tests
}

this.assignSteeringProperties_(steeringManifestJson);
const parsedMetadata = {
contentSteeringInfo: metadata.contentSteeringInfo,
contentSteeringManifest: {
version: this.steeringManifest.version,
reloadUri: this.steeringManifest.reloadUri,
priority: this.steeringManifest.priority
}
};

this.trigger({ type: 'contentsteeringparsed', metadata: parsedMetadata });
this.startTTLTimeout_();
});
}
Expand Down
86 changes: 69 additions & 17 deletions src/dash-playlist-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import {toUint8} from '@videojs/vhs-utils/es/byte-helpers';
import logger from './util/logger';
import {merge} from './util/vjs-compat';
import { getStreamingNetworkErrorMetadata } from './error-codes.js';

const { EventTarget } = videojs;

Expand Down Expand Up @@ -391,20 +392,18 @@
const uri = resolveManifestRedirect(playlist.sidx.resolvedUri);

const fin = (err, request) => {
// TODO: add error metdata here once we create an error type in video.js
if (this.requestErrored_(err, request, startingState)) {
return;
}

const sidxMapping = this.mainPlaylistLoader_.sidxMapping_;
const { requestType } = request;
let sidx;

try {
sidx = parseSidx(toUint8(request.response).subarray(8));
} catch (e) {
e.metadata = {
errorType: videojs.Error.DashManifestSidxParsingError
};
e.metadata = getStreamingNetworkErrorMetadata({requestType, request, parseFailure: true });

// sidx parsing failed.
this.requestErrored_(e, request, startingState);
Expand All @@ -420,6 +419,7 @@

return cb(true);
};
const REQUEST_TYPE = 'dash-sidx';

this.request = containerRequest(uri, this.vhs_.xhr, (err, request, container, bytes) => {
if (err) {
Expand All @@ -439,11 +439,7 @@
internal: true,
playlistExclusionDuration: Infinity,
// MEDIA_ERR_NETWORK
code: 2,
metadata: {
errorType: videojs.Error.UnsupportedSidxContainer,
sidxContainer
}
code: 2
}, request);
}

Expand All @@ -462,9 +458,10 @@
this.request = this.vhs_.xhr({
uri,
responseType: 'arraybuffer',
requestType: 'dash-sidx',
headers: segmentXhrHeaders({byterange: playlist.sidx.byterange})
}, fin);
});
}, REQUEST_TYPE);
}

dispose() {
Expand Down Expand Up @@ -646,17 +643,31 @@
}

requestMain_(cb) {
const metadata = {
manifestInfo: {
uri: this.mainPlaylistLoader_.srcUrl
}
};

this.trigger({type: 'manifestrequeststart', metadata});
this.request = this.vhs_.xhr({
uri: this.mainPlaylistLoader_.srcUrl,
withCredentials: this.withCredentials,
requestType: 'dash-manifest'
}, (error, req) => {
if (error) {
const { requestType } = req;

error.metadata = getStreamingNetworkErrorMetadata({ requestType, request: req, error });
}

if (this.requestErrored_(error, req)) {
if (this.state === 'HAVE_NOTHING') {
this.started = false;
}
return;
}
this.trigger({type: 'manifestrequestcomplete', metadata});

const mainChanged = req.responseText !== this.mainPlaylistLoader_.mainXml_;

Expand Down Expand Up @@ -717,6 +728,9 @@
}

if (error) {
const { requestType } = req;

Check warning on line 731 in src/dash-playlist-loader.js

View check run for this annotation

Codecov / codecov/patch

src/dash-playlist-loader.js#L731

Added line #L731 was not covered by tests

this.error.metadata = getStreamingNetworkErrorMetadata({ requestType, request: req, error });

Check warning on line 733 in src/dash-playlist-loader.js

View check run for this annotation

Codecov / codecov/patch

src/dash-playlist-loader.js#L733

Added line #L733 was not covered by tests
// sync request failed, fall back to using date header from mpd
// TODO: log warning
this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
Expand Down Expand Up @@ -762,14 +776,31 @@
this.mediaRequest_ = null;

const oldMain = this.mainPlaylistLoader_.main;
const metadata = {
manifestInfo: {
uri: this.mainPlaylistLoader_.srcUrl
}
};

let newMain = parseMainXml({
mainXml: this.mainPlaylistLoader_.mainXml_,
srcUrl: this.mainPlaylistLoader_.srcUrl,
clientOffset: this.mainPlaylistLoader_.clientOffset_,
sidxMapping: this.mainPlaylistLoader_.sidxMapping_,
previousManifest: oldMain
});
this.trigger({type: 'manifestparsestart', metadata});
let newMain;

try {
newMain = parseMainXml({
mainXml: this.mainPlaylistLoader_.mainXml_,
srcUrl: this.mainPlaylistLoader_.srcUrl,
clientOffset: this.mainPlaylistLoader_.clientOffset_,
sidxMapping: this.mainPlaylistLoader_.sidxMapping_,
previousManifest: oldMain
});
} catch (error) {
this.error = error;
this.error.metadata = {

Check warning on line 798 in src/dash-playlist-loader.js

View check run for this annotation

Codecov / codecov/patch

src/dash-playlist-loader.js#L797-L798

Added lines #L797 - L798 were not covered by tests
errorType: videojs.Error.StreamingDashManifestParserError,
error
};
this.trigger('error');

Check warning on line 802 in src/dash-playlist-loader.js

View check run for this annotation

Codecov / codecov/patch

src/dash-playlist-loader.js#L802

Added line #L802 was not covered by tests
}

// if we have an old main to compare the new main against
if (oldMain) {
Expand All @@ -789,6 +820,27 @@
}

this.addEventStreamToMetadataTrack_(newMain);
if (newMain) {
const { duration, endList } = newMain;
const renditions = [];

newMain.playlists.forEach((playlist) => {
renditions.push({
id: playlist.id,
bandwidth: playlist.attributes.BANDWIDTH,
resolution: playlist.attributes.RESOLUTION,
codecs: playlist.attributes.CODECS
});
});
const parsedManifest = {
duration,
isLive: !endList,
renditions
};

metadata.parsedManifest = parsedManifest;
this.trigger({type: 'manifestparsecomplete', metadata});
}

return Boolean(newMain);
}
Expand Down
30 changes: 30 additions & 0 deletions src/error-codes.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,32 @@
import videojs from 'video.js';

// https://www.w3.org/TR/WebIDL-1/#quotaexceedederror
export const QUOTA_EXCEEDED_ERR = 22;

export const getStreamingNetworkErrorMetadata = ({ requestType, request, error, parseFailure }) => {
const isBadStatus = request.status < 200 || request.status > 299;
const isFailure = request.status >= 400 && request.status <= 499;
const errorMetadata = {
uri: request.uri,
requestType
};
const isBadStatusOrParseFailure = (isBadStatus && !isFailure) || parseFailure;

if (error && isFailure) {
// copy original error and add to the metadata.
errorMetadata.error = {...error};
errorMetadata.errorType = videojs.Error.NetworkRequestFailed;
} else if (request.aborted) {
errorMetadata.errorType = videojs.Error.NetworkRequestAborted;
} else if (request.timedout) {
errorMetadata.erroType = videojs.Error.NetworkRequestTimeout;
} else if (isBadStatusOrParseFailure) {
const errorType = parseFailure ? videojs.Error.NetworkBodyParserFailed : videojs.Error.NetworkBadStatus;

errorMetadata.errorType = errorType;
errorMetadata.status = request.status;
errorMetadata.headers = request.headers;
}

return errorMetadata;
};
Loading
Loading