dash.js (v4.1.0) 的请求&放弃请求逻辑


核心逻辑

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】schedule()

  • 【ScheduleController】_shouldScheduleNextRequest()
  • 分支1:【AbrController】checkPlaybackQuality(type, streamId) // ABR逻辑
    • 【ABRRulesCollection】getMaxQuality(rulesContext)
      • 【ABRRules】getMaxIndex(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)
    • eventBus.trigger(Events.MEDIA_FRAGMENT_NEEDED, {}, { streamId: streamInfo.id, mediaType: type }); // 不切换码率时
      • 【StreamProcessor】_onMediaFragmentNeeded(e, rescheduleIfNoRequest = true)
        • 【StreamProcessor】_mediaRequestGenerated(request)
          • 【FragmentModel】executeRequest(request)

请求视频块相关的函数调用链:

  • 【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}); // 判断是否终止请求,触发事件
        • *eventBus.trigger(events.LOADING_PROGRESS, {request: request, stream: event.stream, streamId}); // 判断是否终止请求相关,注册触发事件
        • *eventBus.trigger(events.LOADING_ABANDONED, {mediaType: request.mediaType, request: request, sender: instance}); // 终止请求相关,注册触发事件

request生成相关的函数调用链:

  • 【StreamProcessor】_onInitFragmentNeeded(e, rescheduleIfNoRequest = true)
    • 【DashHandler】getInitRequest(mediaInfo, representation)
      • _generateInitRequest(mediaInfo, representation, mediaType)
  • 【StreamProcessor】_onMediaFragmentNeeded(e, rescheduleIfNoRequest = true)
    • 【StreamProcessor】_getFragmentRequest()
      • 【DashHandler】getSegmentRequestForTime(mediaInfo, representation, time)
        • _getRequestForSegment(mediaInfo, segment)
      • 【DashHandler】getNextSegmentRequestIdempotent(mediaInfo, representation)
        • _getRequestForSegment(mediaInfo, segment)

判断是否终止请求

事件: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() // 终止当前请求

终止请求

事件: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) // 特殊情况处理
            • 【XHRLoader】abort(request)(非直播) // 终止请求
              • XMLHttpRequest.abort()

核心代码

播放主逻辑代码

【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()
}

猜你喜欢

转载自blog.csdn.net/LvGreat/article/details/121252622