UNPKG

@l5i/dashjs

Version:

A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.

444 lines (353 loc) 14.7 kB
/** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ import Constants from '../constants/Constants'; import MetricsConstants from '../constants/MetricsConstants'; import MetricsList from '../vo/MetricsList'; import TCPConnection from '../vo/metrics/TCPConnection'; import {HTTPRequest, HTTPRequestTrace} from '../vo/metrics/HTTPRequest'; import TrackSwitch from '../vo/metrics/RepresentationSwitch'; import BufferLevel from '../vo/metrics/BufferLevel'; import BufferState from '../vo/metrics/BufferState'; import DVRInfo from '../vo/metrics/DVRInfo'; import DroppedFrames from '../vo/metrics/DroppedFrames'; import {ManifestUpdate, ManifestUpdateStreamInfo, ManifestUpdateRepresentationInfo} from '../vo/metrics/ManifestUpdate'; import SchedulingInfo from '../vo/metrics/SchedulingInfo'; import EventBus from '../../core/EventBus'; import RequestsQueue from '../vo/metrics/RequestsQueue'; import Events from '../../core/events/Events'; import FactoryMaker from '../../core/FactoryMaker'; function MetricsModel() { const MAXIMUM_LIST_DEPTH = 1000; let context = this.context; let eventBus = EventBus(context).getInstance(); let instance, adapter, streamMetrics; function setup() { streamMetrics = {}; } function setConfig(config) { if (!config) return; if (config.adapter) { adapter = config.adapter; } } function metricsChanged() { eventBus.trigger(Events.METRICS_CHANGED); } function metricChanged(mediaType) { eventBus.trigger(Events.METRIC_CHANGED, {mediaType: mediaType}); metricsChanged(); } function metricUpdated(mediaType, metricType, vo) { eventBus.trigger(Events.METRIC_UPDATED, {mediaType: mediaType, metric: metricType, value: vo}); metricChanged(mediaType); } function metricAdded(mediaType, metricType, vo) { eventBus.trigger(Events.METRIC_ADDED, {mediaType: mediaType, metric: metricType, value: vo}); metricChanged(mediaType); } function clearCurrentMetricsForType(type) { delete streamMetrics[type]; metricChanged(type); } function clearAllCurrentMetrics() { streamMetrics = {}; metricsChanged(); } function getReadOnlyMetricsFor(type) { if (streamMetrics.hasOwnProperty(type)) { return streamMetrics[type]; } return null; } function getMetricsFor(type) { let metrics; if (streamMetrics.hasOwnProperty(type)) { metrics = streamMetrics[type]; } else { metrics = new MetricsList(); streamMetrics[type] = metrics; } return metrics; } function pushMetrics(type, list, value) { let metrics = getMetricsFor(type); metrics[list].push(value); if ( metrics[list].length > MAXIMUM_LIST_DEPTH ) { metrics[list].shift(); } } function addTcpConnection(mediaType, tcpid, dest, topen, tclose, tconnect) { let vo = new TCPConnection(); vo.tcpid = tcpid; vo.dest = dest; vo.topen = topen; vo.tclose = tclose; vo.tconnect = tconnect; pushAndNotify(mediaType, MetricsConstants.TCP_CONNECTION, vo); return vo; } function appendHttpTrace(httpRequest, s, d, b) { let vo = new HTTPRequestTrace(); vo.s = s; vo.d = d; vo.b = b; httpRequest.trace.push(vo); if (!httpRequest.interval) { httpRequest.interval = 0; } httpRequest.interval += d; return vo; } function addHttpRequest(mediaType, tcpid, type, url, actualurl, serviceLocation, range, trequest, tresponse, tfinish, responsecode, mediaduration, responseHeaders, traces) { let vo = new HTTPRequest(); // ISO 23009-1 D.4.3 NOTE 2: // All entries for a given object will have the same URL and range // and so can easily be correlated. If there were redirects or // failures there will be one entry for each redirect/failure. // The redirect-to URL or alternative url (where multiple have been // provided in the MPD) will appear as the actualurl of the next // entry with the same url value. if (actualurl && (actualurl !== url)) { // given the above, add an entry for the original request addHttpRequest( mediaType, null, type, url, null, null, range, trequest, null, // unknown null, // unknown null, // unknown, probably a 302 mediaduration, null, null ); vo.actualurl = actualurl; } vo.tcpid = tcpid; vo.type = type; vo.url = url; vo.range = range; vo.trequest = trequest; vo.tresponse = tresponse; vo.responsecode = responsecode; vo._tfinish = tfinish; vo._stream = mediaType; vo._mediaduration = mediaduration; vo._responseHeaders = responseHeaders; vo._serviceLocation = serviceLocation; if (traces) { traces.forEach(trace => { appendHttpTrace(vo, trace.s, trace.d, trace.b); }); } else { // The interval and trace shall be absent for redirect and failure records. delete vo.interval; delete vo.trace; } pushAndNotify(mediaType, MetricsConstants.HTTP_REQUEST, vo); return vo; } function addRepresentationSwitch(mediaType, t, mt, to, lto) { let vo = new TrackSwitch(); vo.t = t; vo.mt = mt; vo.to = to; if (lto) { vo.lto = lto; } else { delete vo.lto; } pushAndNotify(mediaType, MetricsConstants.TRACK_SWITCH, vo); return vo; } function pushAndNotify(mediaType, metricType, metricObject) { pushMetrics(mediaType, metricType, metricObject); metricAdded(mediaType, metricType, metricObject); } function addBufferLevel(mediaType, t, level) { let vo = new BufferLevel(); vo.t = t; vo.level = level; pushAndNotify(mediaType, MetricsConstants.BUFFER_LEVEL, vo); return vo; } function addBufferState(mediaType, state, target) { let vo = new BufferState(); vo.target = target; vo.state = state; pushAndNotify(mediaType, MetricsConstants.BUFFER_STATE, vo); return vo; } function addDVRInfo(mediaType, currentTime, mpd, range) { let vo = new DVRInfo(); vo.time = currentTime ; vo.range = range; vo.manifestInfo = mpd; pushAndNotify(mediaType, MetricsConstants.DVR_INFO, vo); return vo; } function addDroppedFrames(mediaType, quality) { let vo = new DroppedFrames(); let list = getMetricsFor(mediaType).DroppedFrames; vo.time = quality.creationTime; vo.droppedFrames = quality.droppedVideoFrames; if (list.length > 0 && list[list.length - 1] == vo) { return list[list.length - 1]; } pushAndNotify(mediaType, MetricsConstants.DROPPED_FRAMES, vo); return vo; } function addSchedulingInfo(mediaType, t, type, startTime, availabilityStartTime, duration, quality, range, state) { let vo = new SchedulingInfo(); vo.mediaType = mediaType; vo.t = t; vo.type = type; vo.startTime = startTime; vo.availabilityStartTime = availabilityStartTime; vo.duration = duration; vo.quality = quality; vo.range = range; vo.state = state; pushAndNotify(mediaType, MetricsConstants.SCHEDULING_INFO, vo); return vo; } function addRequestsQueue(mediaType, loadingRequests, executedRequests) { let vo = new RequestsQueue(); vo.loadingRequests = loadingRequests; vo.executedRequests = executedRequests; getMetricsFor(mediaType).RequestsQueue = vo; metricAdded(mediaType, MetricsConstants.REQUESTS_QUEUE, vo); } function addManifestUpdate(mediaType, type, requestTime, fetchTime, availabilityStartTime, presentationStartTime, clientTimeOffset, currentTime, buffered, latency) { let vo = new ManifestUpdate(); vo.mediaType = mediaType; vo.type = type; vo.requestTime = requestTime; // when this manifest update was requested vo.fetchTime = fetchTime; // when this manifest update was received vo.availabilityStartTime = availabilityStartTime; vo.presentationStartTime = presentationStartTime; // the seek point (liveEdge for dynamic, Stream[0].startTime for static) vo.clientTimeOffset = clientTimeOffset; // the calculated difference between the server and client wall clock time vo.currentTime = currentTime; // actual element.currentTime vo.buffered = buffered; // actual element.ranges vo.latency = latency; // (static is fixed value of zero. dynamic should be ((Now-@availabilityStartTime) - currentTime) pushMetrics(Constants.STREAM, MetricsConstants.MANIFEST_UPDATE, vo); metricAdded(mediaType, MetricsConstants.MANIFEST_UPDATE, vo); return vo; } function updateManifestUpdateInfo(manifestUpdate, updatedFields) { if (manifestUpdate) { for (let field in updatedFields) { manifestUpdate[field] = updatedFields[field]; } metricUpdated(manifestUpdate.mediaType, MetricsConstants.MANIFEST_UPDATE, manifestUpdate); } } function addManifestUpdateStreamInfo(manifestUpdate, id, index, start, duration) { if (manifestUpdate) { let vo = new ManifestUpdateStreamInfo(); vo.id = id; vo.index = index; vo.start = start; vo.duration = duration; manifestUpdate.streamInfo.push(vo); metricUpdated(manifestUpdate.mediaType, MetricsConstants.MANIFEST_UPDATE_STREAM_INFO, manifestUpdate); return vo; } return null; } function addManifestUpdateRepresentationInfo(manifestUpdate, id, index, streamIndex, mediaType, presentationTimeOffset, startNumber, fragmentInfoType) { if (manifestUpdate) { const vo = new ManifestUpdateRepresentationInfo(); vo.id = id; vo.index = index; vo.streamIndex = streamIndex; vo.mediaType = mediaType; vo.startNumber = startNumber; vo.fragmentInfoType = fragmentInfoType; vo.presentationTimeOffset = presentationTimeOffset; manifestUpdate.representationInfo.push(vo); metricUpdated(manifestUpdate.mediaType, MetricsConstants.MANIFEST_UPDATE_TRACK_INFO, manifestUpdate); return vo; } return null; } function addPlayList(vo) { let type = Constants.STREAM; if (vo.trace && Array.isArray(vo.trace)) { vo.trace.forEach(trace => { if (trace.hasOwnProperty('subreplevel') && !trace.subreplevel) { delete trace.subreplevel; } }); } else { delete vo.trace; } pushAndNotify(type, MetricsConstants.PLAY_LIST, vo); return vo; } function addDVBErrors(vo) { let type = Constants.STREAM; pushAndNotify(type, MetricsConstants.DVB_ERRORS, vo); return vo; } instance = { clearCurrentMetricsForType: clearCurrentMetricsForType, clearAllCurrentMetrics: clearAllCurrentMetrics, getReadOnlyMetricsFor: getReadOnlyMetricsFor, getMetricsFor: getMetricsFor, addTcpConnection: addTcpConnection, addHttpRequest: addHttpRequest, addRepresentationSwitch: addRepresentationSwitch, addBufferLevel: addBufferLevel, addBufferState: addBufferState, addDVRInfo: addDVRInfo, addDroppedFrames: addDroppedFrames, addSchedulingInfo: addSchedulingInfo, addRequestsQueue: addRequestsQueue, addManifestUpdate: addManifestUpdate, updateManifestUpdateInfo: updateManifestUpdateInfo, addManifestUpdateStreamInfo: addManifestUpdateStreamInfo, addManifestUpdateRepresentationInfo: addManifestUpdateRepresentationInfo, addPlayList: addPlayList, addDVBErrors: addDVBErrors, setConfig: setConfig }; setup(); return instance; } MetricsModel.__dashjs_factory_name = 'MetricsModel'; export default FactoryMaker.getSingletonFactory(MetricsModel);