UNPKG

dashjs

Version:

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

1,209 lines (982 loc) 142 kB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(_dereq_,module,exports){ /** * 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. */ /** * @class * @ignore */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } var ErrorsBase = (function () { function ErrorsBase() { _classCallCheck(this, ErrorsBase); } _createClass(ErrorsBase, [{ key: 'extend', value: function extend(errors, config) { if (!errors) return; var override = config ? config.override : false; var publicOnly = config ? config.publicOnly : false; for (var err in errors) { if (!errors.hasOwnProperty(err) || this[err] && !override) continue; if (publicOnly && errors[err].indexOf('public_') === -1) continue; this[err] = errors[err]; } } }]); return ErrorsBase; })(); exports['default'] = ErrorsBase; module.exports = exports['default']; },{}],2:[function(_dereq_,module,exports){ /** * 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. */ /** * @class * @ignore */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } var EventsBase = (function () { function EventsBase() { _classCallCheck(this, EventsBase); } _createClass(EventsBase, [{ key: 'extend', value: function extend(events, config) { if (!events) return; var override = config ? config.override : false; var publicOnly = config ? config.publicOnly : false; for (var evt in events) { if (!events.hasOwnProperty(evt) || this[evt] && !override) continue; if (publicOnly && events[evt].indexOf('public_') === -1) continue; this[evt] = events[evt]; } } }]); return EventsBase; })(); exports['default'] = EventsBase; module.exports = exports['default']; },{}],3:[function(_dereq_,module,exports){ /** * 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. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var _coreEventsEventsBase = _dereq_(2); var _coreEventsEventsBase2 = _interopRequireDefault(_coreEventsEventsBase); var MssEvents = (function (_EventsBase) { _inherits(MssEvents, _EventsBase); function MssEvents() { _classCallCheck(this, MssEvents); _get(Object.getPrototypeOf(MssEvents.prototype), 'constructor', this).call(this); this.FRAGMENT_INFO_LOADING_COMPLETED = 'fragmentInfoLoadingCompleted'; } return MssEvents; })(_coreEventsEventsBase2['default']); var mssEvents = new MssEvents(); exports['default'] = mssEvents; module.exports = exports['default']; },{"2":2}],4:[function(_dereq_,module,exports){ /** * 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. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _MssEvents = _dereq_(3); var _MssEvents2 = _interopRequireDefault(_MssEvents); var _MssFragmentMoofProcessor = _dereq_(5); var _MssFragmentMoofProcessor2 = _interopRequireDefault(_MssFragmentMoofProcessor); var _streamingVoFragmentRequest = _dereq_(15); var _streamingVoFragmentRequest2 = _interopRequireDefault(_streamingVoFragmentRequest); function MssFragmentInfoController(config) { config = config || {}; var context = this.context; var instance = undefined, logger = undefined, fragmentModel = undefined, started = undefined, type = undefined, bufferTimeout = undefined, startTime = undefined, startFragmentTime = undefined, index = undefined; var streamProcessor = config.streamProcessor; var eventBus = config.eventBus; var dashMetrics = config.dashMetrics; var playbackController = config.playbackController; var ISOBoxer = config.ISOBoxer; var baseURLController = config.baseURLController; var debug = config.debug; var controllerType = 'MssFragmentInfoController'; function setup() { logger = debug.getLogger(instance); } function initialize() { started = false; startTime = null; startFragmentTime = null; // Register to StreamProcessor as external controller streamProcessor.registerExternalController(instance); type = streamProcessor.getType(); fragmentModel = streamProcessor.getFragmentModel(); } function doStart() { if (started === true) { return; } logger.debug('Do start'); eventBus.on(_MssEvents2['default'].FRAGMENT_INFO_LOADING_COMPLETED, onFragmentInfoLoadedCompleted, instance); started = true; startTime = new Date().getTime(); index = 0; loadNextFragmentInfo(); } function doStop() { if (!started) { return; } logger.debug('Do stop'); eventBus.off(_MssEvents2['default'].FRAGMENT_INFO_LOADING_COMPLETED, onFragmentInfoLoadedCompleted, instance); // Stop buffering process clearTimeout(bufferTimeout); started = false; startTime = null; startFragmentTime = null; } function reset() { doStop(); streamProcessor.unregisterExternalController(instance); } function loadNextFragmentInfo() { // Check if running state if (!started) { return; } // Get last segment from SegmentTimeline var representation = getCurrentRepresentation(); var manifest = representation.adaptation.period.mpd.manifest; var adaptation = manifest.Period_asArray[representation.adaptation.period.index].AdaptationSet_asArray[representation.adaptation.index]; var segments = adaptation.SegmentTemplate.SegmentTimeline.S_asArray; var segment = segments[segments.length - 1]; logger.debug('Last fragment time: ' + segment.t / adaptation.SegmentTemplate.timescale); // Generate segment request var request = getRequestForSegment(adaptation, representation, segment); // Send segment request requestFragment.call(this, request); } function delayLoadNextFragmentInfo(delay) { clearTimeout(bufferTimeout); bufferTimeout = setTimeout(function () { bufferTimeout = null; loadNextFragmentInfo(); }, delay * 1000); } function getRequestForSegment(adaptation, representation, segment) { var timescale = adaptation.SegmentTemplate.timescale; var request = new _streamingVoFragmentRequest2['default'](); request.mediaType = type; request.type = 'FragmentInfoSegment'; // request.range = segment.mediaRange; request.startTime = segment.t / timescale; request.duration = segment.d / timescale; request.timescale = timescale; // request.availabilityStartTime = segment.availabilityStartTime; // request.availabilityEndTime = segment.availabilityEndTime; // request.wallStartTime = segment.wallStartTime; request.quality = representation.index; request.index = index++; request.mediaInfo = streamProcessor.getMediaInfo(); request.adaptationIndex = representation.adaptation.index; request.representationId = representation.id; request.url = baseURLController.resolve(representation.path).url + adaptation.SegmentTemplate.media; request.url = request.url.replace('$Bandwidth$', representation.bandwidth); request.url = request.url.replace('$Time$', segment.tManifest ? segment.tManifest : segment.t); request.url = request.url.replace('/Fragments(', '/FragmentInfo('); return request; } function getCurrentRepresentation() { var representationController = streamProcessor.getRepresentationController(); var representation = representationController.getCurrentRepresentation(); return representation; } function requestFragment(request) { logger.debug('Load fragment for time: ' + request.startTime); if (streamProcessor.getFragmentModel().isFragmentLoadedOrPending(request)) { // We may have reached end of timeline in case of start-over streams logger.debug('No more fragments'); return; } fragmentModel.executeRequest(request); } function onFragmentInfoLoadedCompleted(e) { if (e.streamProcessor !== streamProcessor) { return; } var request = e.fragmentInfo.request; if (!e.fragmentInfo.response) { logger.error('Load error', request.url); return; } var deltaFragmentTime = undefined, deltaTime = undefined; logger.debug('FragmentInfo loaded: ', request.url); if (!startFragmentTime) { startFragmentTime = request.startTime; } try { // Process FramgentInfo in order to update segment timeline (DVR window) var mssFragmentMoofProcessor = (0, _MssFragmentMoofProcessor2['default'])(context).create({ dashMetrics: dashMetrics, playbackController: playbackController, ISOBoxer: ISOBoxer, eventBus: eventBus, debug: debug }); mssFragmentMoofProcessor.updateSegmentList(e.fragmentInfo, streamProcessor); deltaTime = (new Date().getTime() - startTime) / 1000; deltaFragmentTime = request.startTime + request.duration - startFragmentTime; delayLoadNextFragmentInfo(Math.max(0, deltaFragmentTime - deltaTime)); } catch (e) { logger.fatal('Internal error while processing fragment info segment '); } } function getType() { return type; } instance = { initialize: initialize, controllerType: controllerType, start: doStart, getType: getType, reset: reset }; setup(); return instance; } MssFragmentInfoController.__dashjs_factory_name = 'MssFragmentInfoController'; exports['default'] = dashjs.FactoryMaker.getClassFactory(MssFragmentInfoController); /* jshint ignore:line */ module.exports = exports['default']; },{"15":15,"3":3,"5":5}],5:[function(_dereq_,module,exports){ /** * 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. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _streamingVoDashJSError = _dereq_(13); var _streamingVoDashJSError2 = _interopRequireDefault(_streamingVoDashJSError); var _errorsMssErrors = _dereq_(9); var _errorsMssErrors2 = _interopRequireDefault(_errorsMssErrors); var _streamingMediaPlayerEvents = _dereq_(12); var _streamingMediaPlayerEvents2 = _interopRequireDefault(_streamingMediaPlayerEvents); /** * @module MssFragmentMoofProcessor * @ignore * @param {Object} config object */ function MssFragmentMoofProcessor(config) { config = config || {}; var instance = undefined, type = undefined, logger = undefined; var dashMetrics = config.dashMetrics; var playbackController = config.playbackController; var errorHandler = config.errHandler; var eventBus = config.eventBus; var ISOBoxer = config.ISOBoxer; var debug = config.debug; function setup() { logger = debug.getLogger(instance); type = ''; } function processTfrf(request, tfrf, tfdt, streamProcessor) { var representationController = streamProcessor.getRepresentationController(); var representation = representationController.getCurrentRepresentation(); var indexHandler = streamProcessor.getIndexHandler(); var manifest = representation.adaptation.period.mpd.manifest; var adaptation = manifest.Period_asArray[representation.adaptation.period.index].AdaptationSet_asArray[representation.adaptation.index]; var timescale = adaptation.SegmentTemplate.timescale; type = streamProcessor.getType(); if (manifest.type !== 'dynamic' && !manifest.timeShiftBufferDepth) { return; } if (!tfrf) { errorHandler.error(new _streamingVoDashJSError2['default'](_errorsMssErrors2['default'].MSS_NO_TFRF_CODE, _errorsMssErrors2['default'].MSS_NO_TFRF_MESSAGE)); return; } // Get adaptation's segment timeline (always a SegmentTimeline in Smooth Streaming use case) var segments = adaptation.SegmentTemplate.SegmentTimeline.S; var entries = tfrf.entry; var entry = undefined, segmentTime = undefined, range = undefined; var segment = null; var t = 0; var availabilityStartTime = null; if (entries.length === 0) { return; } // Consider only first tfrf entry (to avoid pre-condition failure on fragment info requests) entry = entries[0]; // In case of start-over streams, check if we have reached end of original manifest duration (set in timeShiftBufferDepth) // => then do not update anymore timeline if (manifest.type === 'static') { // Get first segment time segmentTime = segments[0].tManifest ? parseFloat(segments[0].tManifest) : segments[0].t; if (entry.fragment_absolute_time > segmentTime + manifest.timeShiftBufferDepth * timescale) { return; } } logger.debug('entry - t = ', entry.fragment_absolute_time / timescale); // Get last segment time segmentTime = segments[segments.length - 1].tManifest ? parseFloat(segments[segments.length - 1].tManifest) : segments[segments.length - 1].t; logger.debug('Last segment - t = ', segmentTime / timescale); // Check if we have to append new segment to timeline if (entry.fragment_absolute_time <= segmentTime) { // Update DVR window range // => set range end to end time of current segment range = { start: segments[0].t / timescale, end: tfdt.baseMediaDecodeTime / timescale + request.duration }; updateDVR(request.mediaType, range, streamProcessor.getStreamInfo().manifestInfo); return; } logger.debug('Add new segment - t = ', entry.fragment_absolute_time / timescale); segment = {}; segment.t = entry.fragment_absolute_time; segment.d = entry.fragment_duration; // If timestamps starts at 0 relative to 1st segment (dynamic to static) then update segment time if (segments[0].tManifest) { segment.t -= parseFloat(segments[0].tManifest) - segments[0].t; segment.tManifest = entry.fragment_absolute_time; } segments.push(segment); // In case of static start-over streams, update content duration if (manifest.type === 'static') { if (type === 'video') { segment = segments[segments.length - 1]; var end = (segment.t + segment.d) / timescale; if (end > representation.adaptation.period.duration) { eventBus.trigger(_streamingMediaPlayerEvents2['default'].MANIFEST_VALIDITY_CHANGED, { sender: this, newDuration: end }); } } return; } // In case of live streams, update segment timeline according to DVR window else if (manifest.timeShiftBufferDepth && manifest.timeShiftBufferDepth > 0) { // Get timestamp of the last segment segment = segments[segments.length - 1]; t = segment.t; // Determine the segments' availability start time availabilityStartTime = Math.round((t - manifest.timeShiftBufferDepth * timescale) / timescale); // Remove segments prior to availability start time segment = segments[0]; while (Math.round(segment.t / timescale) < availabilityStartTime) { logger.debug('Remove segment - t = ' + segment.t / timescale); segments.splice(0, 1); segment = segments[0]; } // Update DVR window range => set range end to end time of current segment range = { start: segments[0].t / timescale, end: tfdt.baseMediaDecodeTime / timescale + request.duration }; updateDVR(type, range, streamProcessor.getStreamInfo().manifestInfo); } indexHandler.updateRepresentation(representation, true); } function updateDVR(type, range, manifestInfo) { var dvrInfos = dashMetrics.getCurrentDVRInfo(type); if (!dvrInfos || range.end > dvrInfos.range.end) { logger.debug('Update DVR Infos [' + range.start + ' - ' + range.end + ']'); dashMetrics.addDVRInfo(type, playbackController.getTime(), manifestInfo, range); } } // This function returns the offset of the 1st byte of a child box within a container box function getBoxOffset(parent, type) { var offset = 8; var i = 0; for (i = 0; i < parent.boxes.length; i++) { if (parent.boxes[i].type === type) { return offset; } offset += parent.boxes[i].size; } return offset; } function convertFragment(e, sp) { var i = undefined; // e.request contains request description object // e.response contains fragment bytes var isoFile = ISOBoxer.parseBuffer(e.response); // Update track_Id in tfhd box var tfhd = isoFile.fetch('tfhd'); tfhd.track_ID = e.request.mediaInfo.index + 1; // Add tfdt box var tfdt = isoFile.fetch('tfdt'); var traf = isoFile.fetch('traf'); if (tfdt === null) { tfdt = ISOBoxer.createFullBox('tfdt', traf, tfhd); tfdt.version = 1; tfdt.flags = 0; tfdt.baseMediaDecodeTime = Math.floor(e.request.startTime * e.request.timescale); } var trun = isoFile.fetch('trun'); // Process tfxd boxes // This box provide absolute timestamp but we take the segment start time for tfdt var tfxd = isoFile.fetch('tfxd'); if (tfxd) { tfxd._parent.boxes.splice(tfxd._parent.boxes.indexOf(tfxd), 1); tfxd = null; } var tfrf = isoFile.fetch('tfrf'); processTfrf(e.request, tfrf, tfdt, sp); if (tfrf) { tfrf._parent.boxes.splice(tfrf._parent.boxes.indexOf(tfrf), 1); tfrf = null; } // If protected content in PIFF1.1 format (sepiff box = Sample Encryption PIFF) // => convert sepiff box it into a senc box // => create saio and saiz boxes (if not already present) var sepiff = isoFile.fetch('sepiff'); if (sepiff !== null) { sepiff.type = 'senc'; sepiff.usertype = undefined; var _saio = isoFile.fetch('saio'); if (_saio === null) { // Create Sample Auxiliary Information Offsets Box box (saio) _saio = ISOBoxer.createFullBox('saio', traf); _saio.version = 0; _saio.flags = 0; _saio.entry_count = 1; _saio.offset = [0]; var saiz = ISOBoxer.createFullBox('saiz', traf); saiz.version = 0; saiz.flags = 0; saiz.sample_count = sepiff.sample_count; saiz.default_sample_info_size = 0; saiz.sample_info_size = []; if (sepiff.flags & 0x02) { // Sub-sample encryption => set sample_info_size for each sample for (i = 0; i < sepiff.sample_count; i += 1) { // 10 = 8 (InitializationVector field size) + 2 (subsample_count field size) // 6 = 2 (BytesOfClearData field size) + 4 (BytesOfEncryptedData field size) saiz.sample_info_size[i] = 10 + 6 * sepiff.entry[i].NumberOfEntries; } } else { // No sub-sample encryption => set default sample_info_size = InitializationVector field size (8) saiz.default_sample_info_size = 8; } } } tfhd.flags &= 0xFFFFFE; // set tfhd.base-data-offset-present to false tfhd.flags |= 0x020000; // set tfhd.default-base-is-moof to true trun.flags |= 0x000001; // set trun.data-offset-present to true // Update trun.data_offset field that corresponds to first data byte (inside mdat box) var moof = isoFile.fetch('moof'); var length = moof.getLength(); trun.data_offset = length + 8; // Update saio box offset field according to new senc box offset var saio = isoFile.fetch('saio'); if (saio !== null) { var trafPosInMoof = getBoxOffset(moof, 'traf'); var sencPosInTraf = getBoxOffset(traf, 'senc'); // Set offset from begin fragment to the first IV field in senc box saio.offset[0] = trafPosInMoof + sencPosInTraf + 16; // 16 = box header (12) + sample_count field size (4) } // Write transformed/processed fragment into request reponse data e.response = isoFile.write(); } function updateSegmentList(e, sp) { // e.request contains request description object // e.response contains fragment bytes if (!e.response) { throw new Error('e.response parameter is missing'); } var isoFile = ISOBoxer.parseBuffer(e.response); // Update track_Id in tfhd box var tfhd = isoFile.fetch('tfhd'); tfhd.track_ID = e.request.mediaInfo.index + 1; // Add tfdt box var tfdt = isoFile.fetch('tfdt'); var traf = isoFile.fetch('traf'); if (tfdt === null) { tfdt = ISOBoxer.createFullBox('tfdt', traf, tfhd); tfdt.version = 1; tfdt.flags = 0; tfdt.baseMediaDecodeTime = Math.floor(e.request.startTime * e.request.timescale); } var tfrf = isoFile.fetch('tfrf'); processTfrf(e.request, tfrf, tfdt, sp); if (tfrf) { tfrf._parent.boxes.splice(tfrf._parent.boxes.indexOf(tfrf), 1); tfrf = null; } } function getType() { return type; } instance = { convertFragment: convertFragment, updateSegmentList: updateSegmentList, getType: getType }; setup(); return instance; } MssFragmentMoofProcessor.__dashjs_factory_name = 'MssFragmentMoofProcessor'; exports['default'] = dashjs.FactoryMaker.getClassFactory(MssFragmentMoofProcessor); /* jshint ignore:line */ module.exports = exports['default']; },{"12":12,"13":13,"9":9}],6:[function(_dereq_,module,exports){ /** * 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. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _errorsMssErrors = _dereq_(9); var _errorsMssErrors2 = _interopRequireDefault(_errorsMssErrors); /** * @module MssFragmentMoovProcessor * @ignore * @param {Object} config object */ function MssFragmentMoovProcessor(config) { config = config || {}; var NALUTYPE_SPS = 7; var NALUTYPE_PPS = 8; var constants = config.constants; var ISOBoxer = config.ISOBoxer; var protectionController = config.protectionController; var instance = undefined, period = undefined, adaptationSet = undefined, representation = undefined, contentProtection = undefined, timescale = undefined, trackId = undefined; function createFtypBox(isoFile) { var ftyp = ISOBoxer.createBox('ftyp', isoFile); ftyp.major_brand = 'iso6'; ftyp.minor_version = 1; // is an informative integer for the minor version of the major brand ftyp.compatible_brands = []; //is a list, to the end of the box, of brands isom, iso6 and msdh ftyp.compatible_brands[0] = 'isom'; // => decimal ASCII value for isom ftyp.compatible_brands[1] = 'iso6'; // => decimal ASCII value for iso6 ftyp.compatible_brands[2] = 'msdh'; // => decimal ASCII value for msdh return ftyp; } function createMoovBox(isoFile) { // moov box var moov = ISOBoxer.createBox('moov', isoFile); // moov/mvhd createMvhdBox(moov); // moov/trak var trak = ISOBoxer.createBox('trak', moov); // moov/trak/tkhd createTkhdBox(trak); // moov/trak/mdia var mdia = ISOBoxer.createBox('mdia', trak); // moov/trak/mdia/mdhd createMdhdBox(mdia); // moov/trak/mdia/hdlr createHdlrBox(mdia); // moov/trak/mdia/minf var minf = ISOBoxer.createBox('minf', mdia); switch (adaptationSet.type) { case constants.VIDEO: // moov/trak/mdia/minf/vmhd createVmhdBox(minf); break; case constants.AUDIO: // moov/trak/mdia/minf/smhd createSmhdBox(minf); break; default: break; } // moov/trak/mdia/minf/dinf var dinf = ISOBoxer.createBox('dinf', minf); // moov/trak/mdia/minf/dinf/dref createDrefBox(dinf); // moov/trak/mdia/minf/stbl var stbl = ISOBoxer.createBox('stbl', minf); // Create empty stts, stsc, stco and stsz boxes // Use data field as for codem-isoboxer unknown boxes for setting fields value // moov/trak/mdia/minf/stbl/stts var stts = ISOBoxer.createFullBox('stts', stbl); stts._data = [0, 0, 0, 0, 0, 0, 0, 0]; // version = 0, flags = 0, entry_count = 0 // moov/trak/mdia/minf/stbl/stsc var stsc = ISOBoxer.createFullBox('stsc', stbl); stsc._data = [0, 0, 0, 0, 0, 0, 0, 0]; // version = 0, flags = 0, entry_count = 0 // moov/trak/mdia/minf/stbl/stco var stco = ISOBoxer.createFullBox('stco', stbl); stco._data = [0, 0, 0, 0, 0, 0, 0, 0]; // version = 0, flags = 0, entry_count = 0 // moov/trak/mdia/minf/stbl/stsz var stsz = ISOBoxer.createFullBox('stsz', stbl); stsz._data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // version = 0, flags = 0, sample_size = 0, sample_count = 0 // moov/trak/mdia/minf/stbl/stsd createStsdBox(stbl); // moov/mvex var mvex = ISOBoxer.createBox('mvex', moov); // moov/mvex/trex createTrexBox(mvex); if (contentProtection && protectionController) { var supportedKS = protectionController.getSupportedKeySystemsFromContentProtection(contentProtection); createProtectionSystemSpecificHeaderBox(moov, supportedKS); } } function createMvhdBox(moov) { var mvhd = ISOBoxer.createFullBox('mvhd', moov); mvhd.version = 1; // version = 1 in order to have 64bits duration value mvhd.creation_time = 0; // the creation time of the presentation => ignore (set to 0) mvhd.modification_time = 0; // the most recent time the presentation was modified => ignore (set to 0) mvhd.timescale = timescale; // the time-scale for the entire presentation => 10000000 for MSS mvhd.duration = Math.round(period.duration * timescale); // the length of the presentation (in the indicated timescale) => take duration of period mvhd.rate = 1.0; // 16.16 number, '1.0' = normal playback mvhd.volume = 1.0; // 8.8 number, '1.0' = full volume mvhd.reserved1 = 0; mvhd.reserved2 = [0x0, 0x0]; mvhd.matrix = [1, 0, 0, // provides a transformation matrix for the video; 0, 1, 0, // (u,v,w) are restricted here to (0,0,1) 0, 0, 16384]; mvhd.pre_defined = [0, 0, 0, 0, 0, 0]; mvhd.next_track_ID = trackId + 1; // indicates a value to use for the track ID of the next track to be added to this presentation return mvhd; } function createTkhdBox(trak) { var tkhd = ISOBoxer.createFullBox('tkhd', trak); tkhd.version = 1; // version = 1 in order to have 64bits duration value tkhd.flags = 0x1 | // Track_enabled (0x000001): Indicates that the track is enabled 0x2 | // Track_in_movie (0x000002): Indicates that the track is used in the presentation 0x4; // Track_in_preview (0x000004): Indicates that the track is used when previewing the presentation tkhd.creation_time = 0; // the creation time of the presentation => ignore (set to 0) tkhd.modification_time = 0; // the most recent time the presentation was modified => ignore (set to 0) tkhd.track_ID = trackId; // uniquely identifies this track over the entire life-time of this presentation tkhd.reserved1 = 0; tkhd.duration = Math.round(period.duration * timescale); // the duration of this track (in the timescale indicated in the Movie Header Box) => take duration of period tkhd.reserved2 = [0x0, 0x0]; tkhd.layer = 0; // specifies the front-to-back ordering of video tracks; tracks with lower numbers are closer to the viewer => 0 since only one video track tkhd.alternate_group = 0; // specifies a group or collection of tracks => ignore tkhd.volume = 1.0; // '1.0' = full volume tkhd.reserved3 = 0; tkhd.matrix = [1, 0, 0, // provides a transformation matrix for the video; 0, 1, 0, // (u,v,w) are restricted here to (0,0,1) 0, 0, 16384]; tkhd.width = representation.width; // visual presentation width tkhd.height = representation.height; // visual presentation height return tkhd; } function createMdhdBox(mdia) { var mdhd = ISOBoxer.createFullBox('mdhd', mdia); mdhd.version = 1; // version = 1 in order to have 64bits duration value mdhd.creation_time = 0; // the creation time of the presentation => ignore (set to 0) mdhd.modification_time = 0; // the most recent time the presentation was modified => ignore (set to 0) mdhd.timescale = timescale; // the time-scale for the entire presentation mdhd.duration = Math.round(period.duration * timescale); // the duration of this media (in the scale of the timescale). If the duration cannot be determined then duration is set to all 1s. mdhd.language = adaptationSet.lang || 'und'; // declares the language code for this media (see getLanguageCode()) mdhd.pre_defined = 0; return mdhd; } function createHdlrBox(mdia) { var hdlr = ISOBoxer.createFullBox('hdlr', mdia); hdlr.pre_defined = 0; switch (adaptationSet.type) { case constants.VIDEO: hdlr.handler_type = 'vide'; break; case constants.AUDIO: hdlr.handler_type = 'soun'; break; default: hdlr.handler_type = 'meta'; break; } hdlr.name = representation.id; hdlr.reserved = [0, 0, 0]; return hdlr; } function createVmhdBox(minf) { var vmhd = ISOBoxer.createFullBox('vmhd', minf); vmhd.flags = 1; vmhd.graphicsmode = 0; // specifies a composition mode for this video track, from the following enumerated set, which may be extended by derived specifications: copy = 0 copy over the existing image vmhd.opcolor = [0, 0, 0]; // is a set of 3 colour values (red, green, blue) available for use by graphics modes return vmhd; } function createSmhdBox(minf) { var smhd = ISOBoxer.createFullBox('smhd', minf); smhd.flags = 1; smhd.balance = 0; // is a fixed-point 8.8 number that places mono audio tracks in a stereo space; 0 is centre (the normal value); full left is -1.0 and full right is 1.0. smhd.reserved = 0; return smhd; } function createDrefBox(dinf) { var dref = ISOBoxer.createFullBox('dref', dinf); dref.entry_count = 1; dref.entries = []; var url = ISOBoxer.createFullBox('url ', dref, false); url.location = ''; url.flags = 1; dref.entries.push(url); return dref; } function createStsdBox(stbl) { var stsd = ISOBoxer.createFullBox('stsd', stbl); stsd.entries = []; switch (adaptationSet.type) { case constants.VIDEO: case constants.AUDIO: stsd.entries.push(createSampleEntry(stsd)); break; default: break; } stsd.entry_count = stsd.entries.length; // is an integer that counts the actual entries return stsd; } function createSampleEntry(stsd) { var codec = representation.codecs.substring(0, representation.codecs.indexOf('.')); switch (codec) { case 'avc1': return createAVCVisualSampleEntry(stsd, codec); case 'mp4a': return createMP4AudioSampleEntry(stsd, codec); default: throw { code: _errorsMssErrors2['default'].MSS_UNSUPPORTED_CODEC_CODE, message: _errorsMssErrors2['default'].MSS_UNSUPPORTED_CODEC_MESSAGE, data: { codec: codec } }; } } function createAVCVisualSampleEntry(stsd, codec) { var avc1 = undefined; if (contentProtection) { avc1 = ISOBoxer.createBox('encv', stsd, false); } else { avc1 = ISOBoxer.createBox('avc1', stsd, false); } // SampleEntry fields avc1.reserved1 = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0]; avc1.data_reference_index = 1; // VisualSampleEntry fields avc1.pre_defined1 = 0; avc1.reserved2 = 0; avc1.pre_defined2 = [0, 0, 0]; avc1.height = representation.height; avc1.width = representation.width; avc1.horizresolution = 72; // 72 dpi avc1.vertresolution = 72; // 72 dpi avc1.reserved3 = 0; avc1.frame_count = 1; // 1 compressed video frame per sample avc1.compressorname = [0x0A, 0x41, 0x56, 0x43, 0x20, 0x43, 0x6F, 0x64, // = 'AVC Coding'; 0x69, 0x6E, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; avc1.depth = 0x0018; // 0x0018 – images are in colour with no alpha. avc1.pre_defined3 = 65535; avc1.config = createAVC1ConfigurationRecord(); if (contentProtection) { // Create and add Protection Scheme Info Box var sinf = ISOBoxer.createBox('sinf', avc1); // Create and add Original Format Box => indicate codec type of the encrypted content createOriginalFormatBox(sinf, codec); // Create and add Scheme Type box createSchemeTypeBox(sinf); // Create and add Scheme Information Box createSchemeInformationBox(sinf); } return avc1; } function createAVC1ConfigurationRecord() { var avcC = null; var avcCLength = 15; // length = 15 by default (0 SPS and 0 PPS)