核心逻辑
dash.js会在下载当前视频块的过程中实时监测下载时间,如果下载时间过长,则可能取消当前视频块的下载,重新请求更低码率的视频块。
核心模块:
- 【判断是否终止】AbandonRequestRule:如果在块下载过程中有卡顿的风险,则取消下载当前块,转而下载码率更低的块以避免卡顿
- 实现:下载过程中,根据已下载的数据量和时间更新吞吐量预测,估计当前视频块的总预期下载时间,在满足以下三个条件时,放弃当前视频块的请求:
- 当前视频块的码率不是最低码率
- 当前视频块的预期下载时间 ≥ 1.8 * 视频块时长(且超过0.5s)【核心判断条件】
- 当前视频块的未下载数据量 > 新选择的视频块大小
- 吞吐量预测:过去若干个采样的平均值
- 选择新码率:根据新的吞吐量预测值进行选择(RB)
- 实现:下载过程中,根据已下载的数据量和时间更新吞吐量预测,估计当前视频块的总预期下载时间,在满足以下三个条件时,放弃当前视频块的请求:
- 【请求&终止请求】HTTPLoader
- internalLoad():发起HTTP请求
- abort():终止当前的HTTP请求
函数调用链
dash.js播放主逻辑
事件:PLAYBACK_STARTED
注册监听器:
- 【StreamController】registerEvents()
- eventBus.on(MediaPlayerEvents.PLAYBACK_STARTED, _onPlaybackStarted, instance);
- 【ScheduleController】initialize(_hasVideoTrack)
- eventBus.on(MediaPlayerEvents.PLAYBACK_STARTED, _onPlaybackStarted, instance);
*注意:这两个_onPlaybackStarted()函数不一样,分别定义在【StreamController】和【ScheduleController】中,在此仅关注【ScheduleController】中的对应函数。
触发事件&函数调用:
- 【PlaybackController】_onPlaybackStart() // 由点击播放按钮触发
- eventBus.trigger(Events.PLAYBACK_STARTED, {startTime: getTime()});
- 【ScheduleController】_onPlaybackStarted()
- 【ScheduleController】startScheduleTimer(value) // 控制调度周期
- setTimeout(schedule, timeoutValue)
- 【ScheduleController】startScheduleTimer(value) // 控制调度周期
- 【ScheduleController】_onPlaybackStarted()
- eventBus.trigger(Events.PLAYBACK_STARTED, {startTime: getTime()});
核心调度函数调用:
【ScheduleController】schedule()
- 【ScheduleController】_shouldScheduleNextRequest()
- 分支1:【AbrController】checkPlaybackQuality(type, streamId) // ABR逻辑
- 【ABRRulesCollection】getMaxQuality(rulesContext)
- 【ABRRules】getMaxIndex(rulesContext)
- 【ABRRulesCollection】getMaxQuality(rulesContext)
- 分支2:【ScheduleController】_getNextFragment() // 请求视频块及终止请求逻辑
*注1:默认设置下,该函数每0.5s调用一次(点播)
*注2:关于dash.js的ABR实现,详见dash.js的ABR逻辑
请求视频块
事件:INIT_FRAGMENT_NEEDED / MEDIA_FRAGMENT_NEEDED
注册监听器:
- 【StreamProcessor】setup()
- eventBus.on(Events.INIT_FRAGMENT_NEEDED, _onInitFragmentNeeded, instance);
- eventBus.on(Events.MEDIA_FRAGMENT_NEEDED, _onMediaFragmentNeeded, instance);
触发事件&函数调用:
- 【ScheduleController】_getNextFragment()
- eventBus.trigger(Events.INIT_FRAGMENT_NEEDED, {representationId: currentRepresentationInfo.id, sender: instance }, { streamId: streamInfo.id, mediaType: type }); // 切换码率时
- 【StreamProcessor】_onInitFragmentNeeded(e, rescheduleIfNoRequest = true)
- 【FragmentModel】executeRequest(request)
- 【StreamProcessor】_onInitFragmentNeeded(e, rescheduleIfNoRequest = true)
- eventBus.trigger(Events.MEDIA_FRAGMENT_NEEDED, {}, { streamId: streamInfo.id, mediaType: type }); // 不切换码率时
- 【StreamProcessor】_onMediaFragmentNeeded(e, rescheduleIfNoRequest = true)
- 【StreamProcessor】_mediaRequestGenerated(request)
- 【FragmentModel】executeRequest(request)
- 【StreamProcessor】_mediaRequestGenerated(request)
- 【StreamProcessor】_onMediaFragmentNeeded(e, rescheduleIfNoRequest = true)
- eventBus.trigger(Events.INIT_FRAGMENT_NEEDED, {representationId: currentRepresentationInfo.id, sender: instance }, { streamId: streamInfo.id, mediaType: type }); // 切换码率时
请求视频块相关的函数调用链:
- 【FragmentModel】executeRequest(request)
- 【FragmentModel】loadCurrentFragment(request)
- 【FragmentLoader】load(request) // 请求与终止请求的核心函数
- 【URLLoader】load(config)
- 【SchemeLoaderFactory】getLoader(url)
- 【HTTPLoader】load(config)
- 【HTTPLoader】internalLoad(config, remainingAttempts)
- 【XHRLoader】load(httpRequest)(非直播)
- XMLHttpRequest.send()
- eventBus.trigger(events.LOADING_PROGRESS, {request: request, stream: event.stream, streamId}); // 判断是否终止请求,触发事件
- 【XHRLoader】load(httpRequest)(非直播)
- 【HTTPLoader】internalLoad(config, remainingAttempts)
- *eventBus.trigger(events.LOADING_PROGRESS, {request: request, stream: event.stream, streamId}); // 判断是否终止请求相关,注册触发事件
- *eventBus.trigger(events.LOADING_ABANDONED, {mediaType: request.mediaType, request: request, sender: instance}); // 终止请求相关,注册触发事件
- 【URLLoader】load(config)
- 【FragmentLoader】load(request) // 请求与终止请求的核心函数
- 【FragmentModel】loadCurrentFragment(request)
request生成相关的函数调用链:
- 【StreamProcessor】_onInitFragmentNeeded(e, rescheduleIfNoRequest = true)
- 【DashHandler】getInitRequest(mediaInfo, representation)
- _generateInitRequest(mediaInfo, representation, mediaType)
- 【DashHandler】getInitRequest(mediaInfo, representation)
- 【StreamProcessor】_onMediaFragmentNeeded(e, rescheduleIfNoRequest = true)
- 【StreamProcessor】_getFragmentRequest()
- 【DashHandler】getSegmentRequestForTime(mediaInfo, representation, time)
- _getRequestForSegment(mediaInfo, segment)
- 【DashHandler】getNextSegmentRequestIdempotent(mediaInfo, representation)
- _getRequestForSegment(mediaInfo, segment)
- 【DashHandler】getSegmentRequestForTime(mediaInfo, representation, time)
- 【StreamProcessor】_getFragmentRequest()
判断是否终止请求
事件:LOADING_PROGRESS
注册监听器:
- 【PlaybackController】_initializeForFirstStream(StreamInfo, compatible)
- eventBus.on(Events.LOADING_PROGRESS, _onFragmentLoadProgress, this);
- 【AbrController】registerStreamType(type, streamProcessor)
- eventBus.on(Events.LOADING_PROGRESS, _onFragmentLoadProgress, instance);
注册触发事件:
- 【FragmentLoader】load(request)
- *【URLLoader】load(config) // 请求相关
触发事件:
- **【FragmentLoader】load(request) **
- 【URLLoader】load(config)
- 【SchemeLoaderFactory】getLoader(url)
- 【HTTPLoader】load(config)
- 【HTTPLoader】internalLoad(config, remainingAttempts)
- eventBus.trigger(events.LOADING_PROGRESS, {request: request, stream: event.stream, streamId});
- 【AbrController】_onFragmentLoadProgress(e)
- 【ABRRulesCollection】shouldAbandonFragment(rulesContext) // 判断是否终止当前请求
- 【AbandonRequestsRule】shouldAbandon // 具体判断规则
- 【FragmentModel】abortRequests() // 终止当前请求
- 【ABRRulesCollection】shouldAbandonFragment(rulesContext) // 判断是否终止当前请求
- 【AbrController】_onFragmentLoadProgress(e)
- eventBus.trigger(events.LOADING_PROGRESS, {request: request, stream: event.stream, streamId});
- 【HTTPLoader】internalLoad(config, remainingAttempts)
- 【URLLoader】load(config)
终止请求
事件:LOADING_ABANDONED
注册监听器:
- 【FragmentModel】setup()
- eventBus.on(events.LOADING_ABANDONED, onLoadingAborted, instance);
注册触发事件:
- 【FragmentLoader】load(request)
- *【URLLoader】load(config) // 请求相关
触发事件:
- 【AbrController】_onFragmentLoadProgress(e)
- 【FragmentModel】abortRequests()
- 【FragmentLoader】abort()
- 【URLLoader】abort()
- 【HTTPLoader】abort()
- eventBus.trigger(events.LOADING_ABANDONED, {mediaType: request.mediaType, request: request, sender: instance});
- 【FragmentModel】onLoadingAborted(e)
- eventBus.trigger(events.FRAGMENT_LOADING_ABANDONED, { request: e.request }, { streamId: streamInfo.id, mediaType: type });
- 【StreamProcessor】_onFragmentLoadingAbandoned(e) // 特殊情况处理
- eventBus.trigger(events.FRAGMENT_LOADING_ABANDONED, { request: e.request }, { streamId: streamInfo.id, mediaType: type });
- 【FragmentModel】onLoadingAborted(e)
- 【XHRLoader】abort(request)(非直播) // 终止请求
- XMLHttpRequest.abort()
- eventBus.trigger(events.LOADING_ABANDONED, {mediaType: request.mediaType, request: request, sender: instance});
- 【HTTPLoader】abort()
- 【URLLoader】abort()
- 【FragmentLoader】abort()
- 【FragmentModel】abortRequests()
核心代码
播放主逻辑代码
【ScheduleController】schedule
【Settings】defaultTimeout:默认值500ms,尝试请求视频块的周期。
function schedule() {
try {
// Check if we are supposed to stop scheduling
if (_shouldClearScheduleTimer()) {
clearScheduleTimer();
return;
}
// 核心逻辑
if (_shouldScheduleNextRequest()) {
let qualityChange = false;
if (checkPlaybackQuality) {
// in case the playback quality is supposed to be changed, the corresponding StreamProcessor will update the currentRepresentation.
// The StreamProcessor will also start the schedule timer again once the quality switch has beeen prepared. Consequently, we only call _getNextFragment if the quality is not changed.
qualityChange = abrController.checkPlaybackQuality(type, streamInfo.id); // 调用ABR
}
if (!qualityChange) {
// dash.js的逻辑:若ABR输出码率与上一视频块码率不同,则重复调用ABR,直到ABR输出保持不变后再请求
_getNextFragment(); // 请求视频块
}
} else {
// 设置定时器,0.5s后重新调用本函数(点播)
startScheduleTimer(settings.get().streaming.lowLatencyEnabled ? settings.get().streaming.scheduling.lowLatencyTimeout : settings.get().streaming.scheduling.defaultTimeout);
}
} catch (e) {
startScheduleTimer(settings.get().streaming.lowLatencyEnabled ? settings.get().streaming.scheduling.lowLatencyTimeout : settings.get().streaming.scheduling.defaultTimeout);
}
}
【FragmentLoader】load
function load(request) {
const report = function (data, error) {
eventBus.trigger(events.LOADING_COMPLETED, {
request: request,
response: data || null,
error: error || null,
sender: instance
});
};
if (request) {
urlLoader.load({
request: request,
progress: function (event) {
// 触发监听器
eventBus.trigger(events.LOADING_PROGRESS, {
request: request,
stream: event.stream,
streamId
});
...
},
...
abort: function (request) {
if (request) {
eventBus.trigger(events.LOADING_ABANDONED, {
mediaType: request.mediaType,
request: request,
sender: instance
});
}
}
});
}
...
}
【AbrController】_onFragmentLoadProgress
/**
* While fragment loading is in progress we check if we might need to abort the request
* @param {object} e
* @private
*/
function _onFragmentLoadProgress(e) {
const type = e.request.mediaType;
const streamId = e.streamId;
if (!type || !streamId || !streamProcessorDict[streamId] || !settings.get().streaming.abr.autoSwitchBitrate[type]) {
return;
}
const streamProcessor = streamProcessorDict[streamId][type];
if (!streamProcessor) {
return;
}
// 调用AbandonRequestsRule,得到新码率
const rulesContext = RulesContext(context).create({
abrController: instance,
streamProcessor: streamProcessor,
currentRequest: e.request,
useBufferOccupancyABR: isUsingBufferOccupancyAbrDict[type],
useL2AABR: isUsingL2AAbrDict[type],
useLoLPABR: isUsingLoLPAbrDict[type],
videoModel
});
const switchRequest = abrRulesCollection.shouldAbandonFragment(rulesContext, streamId);
if (switchRequest.quality > SwitchRequest.NO_CHANGE) {
const fragmentModel = streamProcessor.getFragmentModel();
const request = fragmentModel.getRequests({
state: FragmentModel.FRAGMENT_MODEL_LOADING,
index: e.request.index
})[0];
if (request) {
// 放弃当前视频块,请求新码率视频块
fragmentModel.abortRequests();
abandonmentStateDict[streamId][type].state = MetricsConstants.ABANDON_LOAD;
switchHistoryDict[streamId][type].reset();
switchHistoryDict[streamId][type].push({
oldValue: getQualityFor(type, streamId),
newValue: switchRequest.quality,
confidence: 1,
reason: switchRequest.reason
});
setPlaybackQuality(type, streamController.getActiveStreamInfo(), switchRequest.quality, switchRequest.reason);
clearTimeout(abandonmentTimeout);
abandonmentTimeout = setTimeout(
() => {
abandonmentStateDict[streamId][type].state = MetricsConstants.ALLOW_LOAD;
abandonmentTimeout = null;
},
settings.get().streaming.abandonLoadTimeout // 默认10000s,四舍五入就是应该不会触发?
);
}
}
}
请求代码
【DashHandler】_generateInitRequest
码率改变时构造的request。
function _generateInitRequest(mediaInfo, representation, mediaType) {
const request = new FragmentRequest();
const period = representation.adaptation.period;
const presentationStartTime = period.start;
request.mediaType = mediaType;
request.type = HTTPRequest.INIT_SEGMENT_TYPE;
request.range = representation.range;
request.availabilityStartTime = timelineConverter.calcAvailabilityStartTimeFromPresentationTime(presentationStartTime, representation, isDynamicManifest);
request.availabilityEndTime = timelineConverter.calcAvailabilityEndTimeFromPresentationTime(presentationStartTime + period.duration, representation, isDynamicManifest);
request.quality = representation.index;
request.mediaInfo = mediaInfo;
request.representationId = representation.id;
if (_setRequestUrl(request, representation.initialization, representation)) {
request.url = replaceTokenForTemplate(request.url, 'Bandwidth', representation.bandwidth);
return request;
}
}
【DashHandler】_getRequestForSegment
码率不变时构造的request。
function _getRequestForSegment(mediaInfo, segment) {
if (segment === null || segment === undefined) {
return null;
}
const request = new FragmentRequest();
const representation = segment.representation;
const bandwidth = representation.adaptation.period.mpd.manifest.Period_asArray[representation.adaptation.period.index].AdaptationSet_asArray[representation.adaptation.index].Representation_asArray[representation.index].bandwidth;
let url = segment.media;
url = replaceTokenForTemplate(url, 'Number', segment.replacementNumber);
url = replaceTokenForTemplate(url, 'Time', segment.replacementTime);
url = replaceTokenForTemplate(url, 'Bandwidth', bandwidth);
url = replaceIDForTemplate(url, representation.id);
url = unescapeDollarsInTemplate(url);
request.mediaType = getType();
request.type = HTTPRequest.MEDIA_SEGMENT_TYPE;
request.range = segment.mediaRange;
request.startTime = segment.presentationStartTime;
request.mediaStartTime = segment.mediaStartTime;
request.duration = segment.duration;
request.timescale = representation.timescale;
request.availabilityStartTime = segment.availabilityStartTime;
request.availabilityEndTime = segment.availabilityEndTime;
request.wallStartTime = segment.wallStartTime;
request.quality = representation.index;
request.index = segment.index;
request.mediaInfo = mediaInfo;
request.adaptationIndex = representation.adaptation.index;
request.representationId = representation.id;
if (_setRequestUrl(request, url, representation)) {
return request;
}
}
【HTTPLoader】internalLoad
注:参数中的config来自【FragmentLoader】load(request)中的urlLoader.load()调用传参,包括:request、progress、success、error、abort。其中,progress触发LOADING_DATA_PROGRESS事件,abort触发LOADING_ABANDONED事件,相关代码见上。
function internalLoad(config, remainingAttempts) {
const request = config.request;
...
let httpRequest;
...
request.url = modifiedUrl;
...
// httpRequest结构(注意,其中不包含response)
httpRequest = {
url: modifiedUrl,
method: verb,
withCredentials: withCredentials,
request: request,
onload: onload,
onend: onloadend,
onerror: onloadend,
progress: progress,
onabort: onabort,
ontimeout: ontimeout,
loader: loader,
timeout: requestTimeout,
headers: headers
};
// Adds the ability to delay single fragment loading time to control buffer.
let now = new Date().getTime();
if (isNaN(request.delayLoadingTime) || now >= request.delayLoadingTime) {
// no delay - just send
requests.push(httpRequest); // 将httpRequest放入requests
loader.load(httpRequest); // 由XHRLoader进行load
} else {
// 延迟发送,比如buffer达到目标值以后,不立即发送请求
// delay
let delayedRequest = {
httpRequest: httpRequest };
...
}
}
【XHRLoader】load
function load(httpRequest) {
// Variables will be used in the callback functions
const requestStartTime = new Date();
const request = httpRequest.request;
let xhr = new XMLHttpRequest();
xhr.open(httpRequest.method, httpRequest.url, true);
if (request.responseType) {
xhr.responseType = request.responseType;
}
if (request.range) {
xhr.setRequestHeader('Range', 'bytes=' + request.range);
}
if (!request.requestStartDate) {
request.requestStartDate = requestStartTime;
}
if (requestModifier) {
xhr = requestModifier.modifyRequestHeader(xhr);
}
if (httpRequest.headers) {
for (let header in httpRequest.headers) {
let value = httpRequest.headers[header];
if (value) {
xhr.setRequestHeader(header, value);
}
}
}
xhr.withCredentials = httpRequest.withCredentials;
xhr.onload = httpRequest.onload;
xhr.onloadend = httpRequest.onend;
xhr.onerror = httpRequest.onerror;
xhr.onprogress = httpRequest.progress;
xhr.onabort = httpRequest.onabort;
xhr.ontimeout = httpRequest.ontimeout;
xhr.timeout = httpRequest.timeout;
xhr.send(); // XMLHttpRequest.send()
httpRequest.response = xhr; // 注意,直到这里httpRequest的response才被赋值
}
判断代码
【ABRRulesCollection】shouldAbandonFragment
function shouldAbandonFragment(rulesContext, streamId) {
const abandonRequestArray = abandonFragmentRules.map(rule => rule.shouldAbandon(rulesContext, streamId)); // 调用AbandonRequestsRule
const activeRules = _getRulesWithChange(abandonRequestArray);
const shouldAbandon = getMinSwitchRequest(activeRules); // 按优先级(弱->中->强)确定最终选择的码率
return shouldAbandon || SwitchRequest(context).create();
}
【AbandonRequestsRule】shouldAbandon
【Settings】abandonLoadTimeout:默认值10000s,在ABRController执行期间将阻止切换事件。
const ABANDON_MULTIPLIER = 1.8; // 控制是否放弃当前块的参数
const GRACE_TIME_THRESHOLD = 500; // ms?
const MIN_LENGTH_TO_AVERAGE = 5; // 计算平均吞吐量所使用的采样个数
function shouldAbandon(rulesContext) {
const switchRequest = SwitchRequest(context).create(SwitchRequest.NO_CHANGE, {
name: AbandonRequestsRule.__dashjs_factory_name});
if (!rulesContext || !rulesContext.hasOwnProperty('getMediaInfo') || !rulesContext.hasOwnProperty('getMediaType') || !rulesContext.hasOwnProperty('getCurrentRequest') ||
!rulesContext.hasOwnProperty('getRepresentationInfo') || !rulesContext.hasOwnProperty('getAbrController')) {
return switchRequest;
}
const mediaInfo = rulesContext.getMediaInfo();
const mediaType = rulesContext.getMediaType();
const streamInfo = rulesContext.getStreamInfo();
const streamId = streamInfo ? streamInfo.id : null;
const req = rulesContext.getCurrentRequest(); // 这句很有用,以后可以注意一下
if (!isNaN(req.index)) {
setFragmentRequestDict(mediaType, req.index);
const stableBufferTime = mediaPlayerModel.getStableBufferTime(); // 目标buffer长度,默认12s
const bufferLevel = dashMetrics.getCurrentBufferLevel(mediaType); // 如果buffer足够,则返回
if ( bufferLevel > stableBufferTime ) {
return switchRequest;
}
const fragmentInfo = fragmentDict[mediaType][req.index];
if (fragmentInfo === null || req.firstByteDate === null || abandonDict.hasOwnProperty(fragmentInfo.id)) {
return switchRequest;
}
//setup some init info based on first progress event
if (fragmentInfo.firstByteTime === undefined) {
throughputArray[mediaType] = [];
fragmentInfo.firstByteTime = req.firstByteDate.getTime(); // 请求开始时间?
fragmentInfo.segmentDuration = req.duration; // 视频块时长
fragmentInfo.bytesTotal = req.bytesTotal; // 视频块大小?
fragmentInfo.id = req.index; // 视频块序号?(早知道这些信息这么好获取,当年我干嘛要摸索那么久……)
}
fragmentInfo.bytesLoaded = req.bytesLoaded; // 已下载数据
fragmentInfo.elapsedTime = new Date().getTime() - fragmentInfo.firstByteTime; // 已下载耗时
if (fragmentInfo.bytesLoaded > 0 && fragmentInfo.elapsedTime > 0) {
storeLastRequestThroughputByType(mediaType, Math.round(fragmentInfo.bytesLoaded * 8 / fragmentInfo.elapsedTime)); // 记录吞吐量
}
if (throughputArray[mediaType].length >= MIN_LENGTH_TO_AVERAGE && // 已有5个过去的吞吐量采样,可以计算平均值(预测)
fragmentInfo.elapsedTime > GRACE_TIME_THRESHOLD && // 已下载时间超过0.5s
fragmentInfo.bytesLoaded < fragmentInfo.bytesTotal) {
// 视频块下载未完成
// 预测吞吐量,计算视频块的总预期下载时间
const totalSampledValue = throughputArray[mediaType].reduce((a, b) => a + b, 0);
fragmentInfo.measuredBandwidthInKbps = Math.round(totalSampledValue / throughputArray[mediaType].length);
fragmentInfo.estimatedTimeOfDownload = +((fragmentInfo.bytesTotal * 8 / fragmentInfo.measuredBandwidthInKbps) / 1000).toFixed(2);
// 放弃请求的条件:1. 当前请求的不是最低码率;2. 预期下载时间 ≥ 1.8*视频块时长;3. 未下载数据量 > 新选择的视频块大小
if (fragmentInfo.estimatedTimeOfDownload < fragmentInfo.segmentDuration * ABANDON_MULTIPLIER || rulesContext.getRepresentationInfo().quality === 0 ) {
return switchRequest;
} else if (!abandonDict.hasOwnProperty(fragmentInfo.id)) {
const abrController = rulesContext.getAbrController();
const bytesRemaining = fragmentInfo.bytesTotal - fragmentInfo.bytesLoaded; // 未下载数据量
const bitrateList = abrController.getBitrateList(mediaInfo);
const quality = abrController.getQualityForBitrate(mediaInfo, fragmentInfo.measuredBandwidthInKbps * settings.get().streaming.abr.bandwidthSafetyFactor, streamId); // 根据新预测吞吐量选择安全码率(RB)
const minQuality = abrController.getMinAllowedIndexFor(mediaType, streamId);
const newQuality = (minQuality !== undefined) ? Math.max(minQuality, quality) : quality;
const estimateOtherBytesTotal = fragmentInfo.bytesTotal * bitrateList[newQuality].bitrate / bitrateList[abrController.getQualityFor(mediaType, streamId)].bitrate; // 根据当前块大小和平均码率的比例,估算新视频块的大小
// 当前块剩余数据量 > 新选择的视频块大小
if (bytesRemaining > estimateOtherBytesTotal) {
switchRequest.quality = newQuality;
switchRequest.reason.throughput = fragmentInfo.measuredBandwidthInKbps;
switchRequest.reason.fragmentID = fragmentInfo.id;
abandonDict[fragmentInfo.id] = fragmentInfo;
logger.debug('[' + mediaType + '] frag id',fragmentInfo.id,' is asking to abandon and switch to quality to ', newQuality, ' measured bandwidth was', fragmentInfo.measuredBandwidthInKbps);
delete fragmentDict[mediaType][fragmentInfo.id];
}
}
} else if (fragmentInfo.bytesLoaded === fragmentInfo.bytesTotal) {
delete fragmentDict[mediaType][fragmentInfo.id];
}
}
return switchRequest;
}
终止请求代码
【HTTPLoader】abort
function abort() {
// 取消重试请求
retryRequests.forEach(t => {
clearTimeout(t.timeout);
// abort request in order to trigger LOADING_ABANDONED event
if (t.config.request && t.config.abort) {
t.config.abort(t.config.request);
}
});
retryRequests = [];
// 取消延迟请求
delayedRequests.forEach(x => clearTimeout(x.delayTimeout));
delayedRequests = [];
// 取消已发送请求(对requests中的每一个httpRequest)
requests.forEach(x => {
// MSS patch: ignore FragmentInfo requests
if (x.request.type === HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE) {
return;
}
// abort will trigger onloadend which we don't want
// when deliberately aborting inflight requests -
// set them to undefined so they are not called
x.onloadend = x.onerror = x.onprogress = undefined; // 不调用相关函数
x.loader.abort(x); // 调用XHRLoader的abort()
});
requests = [];
}
【XHRLoader】abort
function abort(request) {
// 这里的request实际上是httpRequest
const x = request.response;
x.onloadend = x.onerror = x.onprogress = undefined; //Ignore events from aborted requests.
x.abort(); // httpRequest.response.abort(),即XMLHttpRequest.abort()
}