Skip to content

Commit

Permalink
feat: streaming events and errors (#1508)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrums86 authored May 16, 2024
1 parent b6ff608 commit c94a230
Show file tree
Hide file tree
Showing 25 changed files with 1,550 additions and 450 deletions.
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 @@ export default class ContentSteeringController extends videojs.EventTarget {
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 @@ export default class ContentSteeringController extends videojs.EventTarget {
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 = {
errorType: videojs.Error.StreamingContentSteeringParserError,
error: parseError
};

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

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 containerRequest from './util/container-request.js';
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 @@ export default class DashPlaylistLoader extends EventTarget {
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 @@ export default class DashPlaylistLoader extends EventTarget {

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 @@ export default class DashPlaylistLoader extends EventTarget {
internal: true,
playlistExclusionDuration: Infinity,
// MEDIA_ERR_NETWORK
code: 2,
metadata: {
errorType: videojs.Error.UnsupportedSidxContainer,
sidxContainer
}
code: 2
}, request);
}

Expand All @@ -462,9 +458,10 @@ export default class DashPlaylistLoader extends EventTarget {
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 @@ export default class DashPlaylistLoader extends EventTarget {
}

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 @@ export default class DashPlaylistLoader extends EventTarget {
}

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

this.error.metadata = getStreamingNetworkErrorMetadata({ requestType, request: req, error });
// 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 @@ export default class DashPlaylistLoader extends EventTarget {
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 = {
errorType: videojs.Error.StreamingDashManifestParserError,
error
};
this.trigger('error');
}

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

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

0 comments on commit c94a230

Please sign in to comment.