1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816 |
- /* global $, Hls, __NETLIFY__ */
- /* eslint camelcase: 0 */
- import { pack } from 'jsonpack';
- import 'promise-polyfill/src/polyfill';
- import { sortObject, copyTextToClipboard } from './demo-utils';
- import { TimelineChart } from './chart/timeline-chart';
- const NETLIFY = __NETLIFY__; // replaced in build
- const STORAGE_KEYS = {
- Editor_Persistence: 'hlsjs:config-editor-persist',
- Hls_Config: 'hlsjs:config',
- volume: 'hlsjs:volume',
- demo_tabs: 'hlsjs:demo-tabs',
- };
- const testStreams = require('../tests/test-streams');
- const defaultTestStreamUrl = testStreams[Object.keys(testStreams)[0]].url;
- const sourceURL = decodeURIComponent(getURLParam('src', defaultTestStreamUrl));
- let demoConfig = getURLParam('demoConfig', null);
- if (demoConfig) {
- demoConfig = JSON.parse(atob(demoConfig));
- } else {
- demoConfig = {};
- }
- const hlsjsDefaults = {
- debug: true,
- enableWorker: true,
- lowLatencyMode: true,
- backBufferLength: 60 * 1.5,
- };
- let enableStreaming = getDemoConfigPropOrDefault('enableStreaming', true);
- let autoRecoverError = getDemoConfigPropOrDefault('autoRecoverError', true);
- let levelCapping = getDemoConfigPropOrDefault('levelCapping', -1);
- let limitMetrics = getDemoConfigPropOrDefault('limitMetrics', -1);
- let dumpfMP4 = getDemoConfigPropOrDefault('dumpfMP4', false);
- let stopOnStall = getDemoConfigPropOrDefault('stopOnStall', false);
- let bufferingIdx = -1;
- let selectedTestStream = null;
- let video = document.querySelector('#video');
- const startTime = Date.now();
- let lastSeekingIdx;
- let lastStartPosition;
- let lastDuration;
- let lastAudioTrackSwitchingIdx;
- let hls;
- let url;
- let events;
- let stats;
- let tracks;
- let fmp4Data;
- let configPersistenceEnabled = false;
- let configEditor = null;
- let chart;
- let resizeAsyncCallbackId = -1;
- const requestAnimationFrame = self.requestAnimationFrame || self.setTimeout;
- const cancelAnimationFrame = self.cancelAnimationFrame || self.clearTimeout;
- const resizeHandlers = [];
- const resize = () => {
- cancelAnimationFrame(resizeAsyncCallbackId);
- resizeAsyncCallbackId = requestAnimationFrame(() => {
- resizeHandlers.forEach((handler) => {
- handler();
- });
- });
- };
- self.onresize = resize;
- if (self.screen && self.screen.orientation) {
- self.screen.orientation.onchange = resize;
- }
- const playerResize = () => {
- const bounds = video.getBoundingClientRect();
- $('#currentSize').html(
- `${Math.round(bounds.width * 10) / 10} x ${
- Math.round(bounds.height * 10) / 10
- }`
- );
- if (video.videoWidth && video.videoHeight) {
- $('#currentResolution').html(`${video.videoWidth} x ${video.videoHeight}`);
- }
- };
- resizeHandlers.push(playerResize);
- $(document).ready(function () {
- setupConfigEditor();
- chart = setupTimelineChart();
- Object.keys(testStreams).forEach((key, index) => {
- const stream = testStreams[key];
- const option = new Option(stream.description, key);
- $('#streamSelect').append(option);
- if (stream.url === sourceURL) {
- document.querySelector('#streamSelect').selectedIndex = index + 1;
- }
- });
- const videoWidth = video.style.width;
- if (videoWidth) {
- $('#videoSize option').each(function (i, option) {
- if (option.value === videoWidth) {
- document.querySelector('#videoSize').selectedIndex = i;
- $('#bufferedCanvas').width(videoWidth);
- resize();
- return false;
- }
- });
- }
- $('#streamSelect').change(function () {
- const key = $('#streamSelect').val() || Object.keys(testStreams)[0];
- selectedTestStream = testStreams[key];
- const streamUrl = selectedTestStream.url;
- $('#streamURL').val(streamUrl);
- loadSelectedStream();
- });
- $('#streamURL').change(function () {
- selectedTestStream = null;
- loadSelectedStream();
- });
- $('#videoSize').change(function () {
- $('#video').width($('#videoSize').val());
- $('#bufferedCanvas').width($('#videoSize').val());
- checkBuffer();
- resize();
- });
- $('#enableStreaming').click(function () {
- enableStreaming = this.checked;
- loadSelectedStream();
- });
- $('#autoRecoverError').click(function () {
- autoRecoverError = this.checked;
- onDemoConfigChanged();
- });
- $('#stopOnStall').click(function () {
- stopOnStall = this.checked;
- onDemoConfigChanged();
- });
- $('#dumpfMP4').click(function () {
- dumpfMP4 = this.checked;
- $('.btn-dump').toggle(dumpfMP4);
- onDemoConfigChanged();
- });
- $('#limitMetrics').change(function () {
- limitMetrics = this.value;
- onDemoConfigChanged();
- });
- $('#levelCapping').change(function () {
- levelCapping = this.value;
- onDemoConfigChanged();
- });
- $('#limitMetrics').val(limitMetrics);
- $('#enableStreaming').prop('checked', enableStreaming);
- $('#autoRecoverError').prop('checked', autoRecoverError);
- $('#stopOnStall').prop('checked', stopOnStall);
- $('#dumpfMP4').prop('checked', dumpfMP4);
- $('#levelCapping').val(levelCapping);
- // link to version on npm if canary
- // github branch for a branch version
- // github tag for a normal tag
- // github PR for a pr
- function getVersionLink(version) {
- const alphaRegex = /[-.]0\.alpha\./;
- if (alphaRegex.test(version)) {
- return `https://www.npmjs.com/package/hls.js/v/${encodeURIComponent(
- version
- )}`;
- } else if (NETLIFY.reviewID) {
- return `https://github.com/video-dev/hls.js/pull/${NETLIFY.reviewID}`;
- } else if (NETLIFY.branch) {
- return `https://github.com/video-dev/hls.js/tree/${encodeURIComponent(
- NETLIFY.branch
- )}`;
- }
- return `https://github.com/video-dev/hls.js/releases/tag/v${encodeURIComponent(
- version
- )}`;
- }
- const version = Hls.version;
- if (version) {
- const $a = $('<a />')
- .attr('target', '_blank')
- .attr('rel', 'noopener noreferrer')
- .attr('href', getVersionLink(version))
- .text('v' + version);
- $('.title').append(' ').append($a);
- }
- $('#streamURL').val(sourceURL);
- const volumeSettings = JSON.parse(
- localStorage.getItem(STORAGE_KEYS.volume)
- ) || {
- volume: 0.05,
- muted: false,
- };
- video.volume = volumeSettings.volume;
- video.muted = volumeSettings.muted;
- $('.btn-dump').toggle(dumpfMP4);
- $('#toggleButtons').show();
- $('#metricsButtonWindow').toggle(self.windowSliding);
- $('#metricsButtonFixed').toggle(!self.windowSliding);
- loadSelectedStream();
- let tabIndexesCSV = localStorage.getItem(STORAGE_KEYS.demo_tabs);
- if (tabIndexesCSV === null) {
- tabIndexesCSV = '0,1,2';
- }
- if (tabIndexesCSV) {
- tabIndexesCSV.split(',').forEach((indexString) => {
- toggleTab($('.demo-tab-btn')[parseInt(indexString) || 0], true);
- });
- }
- $(window).on('popstate', function () {
- window.location.reload();
- });
- });
- function setupGlobals() {
- self.events = events = {
- url: url,
- t0: self.performance.now(),
- load: [],
- buffer: [],
- video: [],
- level: [],
- bitrate: [],
- };
- lastAudioTrackSwitchingIdx = undefined;
- lastSeekingIdx = undefined;
- bufferingIdx = -1;
- // actual values, only on window
- self.recoverDecodingErrorDate = null;
- self.recoverSwapAudioCodecDate = null;
- self.fmp4Data = fmp4Data = {
- audio: [],
- video: [],
- };
- self.onClickBufferedRange = onClickBufferedRange;
- self.updateLevelInfo = updateLevelInfo;
- self.onDemoConfigChanged = onDemoConfigChanged;
- self.createfMP4 = createfMP4;
- self.goToMetricsPermaLink = goToMetricsPermaLink;
- self.toggleTab = toggleTab;
- self.toggleTabClick = toggleTabClick;
- self.applyConfigEditorValue = applyConfigEditorValue;
- }
- function trimArray(target, limit) {
- if (limit < 0) {
- return;
- }
- while (target.length > limit) {
- target.shift();
- }
- }
- function trimEventHistory() {
- const x = limitMetrics;
- if (x < 0) {
- return;
- }
- trimArray(events.load, x);
- trimArray(events.buffer, x);
- trimArray(events.video, x);
- trimArray(events.level, x);
- trimArray(events.bitrate, x);
- }
- function loadSelectedStream() {
- $('#statusOut,#errorOut').empty();
- if (!Hls.isSupported()) {
- handleUnsupported();
- return;
- }
- url = $('#streamURL').val();
- setupGlobals();
- hideCanvas();
- if (hls) {
- hls.destroy();
- clearInterval(hls.bufferTimer);
- hls = null;
- }
- if (!enableStreaming) {
- logStatus('Streaming disabled');
- return;
- }
- logStatus('Loading ' + url);
- // Extending both a demo-specific config and the user config which can override all
- const hlsConfig = $.extend(
- {},
- hlsjsDefaults,
- getEditorValue({ parse: true })
- );
- if (selectedTestStream && selectedTestStream.config) {
- console.info(
- '[loadSelectedStream] extending hls config with stream-specific config: ',
- selectedTestStream.config
- );
- $.extend(hlsConfig, selectedTestStream.config);
- updateConfigEditorValue(hlsConfig);
- }
- onDemoConfigChanged(true);
- console.log('Using Hls.js config:', hlsConfig);
- self.hls = hls = new Hls(hlsConfig);
- logStatus('Loading manifest and attaching video element...');
- const expiredTracks = [].filter.call(
- video.textTracks,
- (track) => track.kind !== 'metadata'
- );
- if (expiredTracks.length) {
- const kinds = expiredTracks
- .map((track) => track.kind)
- .filter((kind, index, self) => self.indexOf(kind) === index);
- logStatus(
- `Replacing video element to remove ${kinds.join(' and ')} text tracks`
- );
- const videoWithExpiredTextTracks = video;
- video = videoWithExpiredTextTracks.cloneNode(false);
- video.removeAttribute('src');
- video.volume = videoWithExpiredTextTracks.volume;
- video.muted = videoWithExpiredTextTracks.muted;
- videoWithExpiredTextTracks.parentNode.insertBefore(
- video,
- videoWithExpiredTextTracks
- );
- videoWithExpiredTextTracks.parentNode.removeChild(
- videoWithExpiredTextTracks
- );
- }
- addChartEventListeners(hls);
- addVideoEventListeners(video);
- hls.loadSource(url);
- hls.autoLevelCapping = levelCapping;
- hls.attachMedia(video);
- hls.on(Hls.Events.MEDIA_ATTACHED, function () {
- logStatus('Media element attached');
- bufferingIdx = -1;
- events.video.push({
- time: self.performance.now() - events.t0,
- type: 'Media attached',
- });
- trimEventHistory();
- });
- hls.on(Hls.Events.MEDIA_DETACHED, function () {
- logStatus('Media element detached');
- clearInterval(hls.bufferTimer);
- bufferingIdx = -1;
- tracks = [];
- events.video.push({
- time: self.performance.now() - events.t0,
- type: 'Media detached',
- });
- trimEventHistory();
- });
- hls.on(Hls.Events.DESTROYING, function () {
- clearInterval(hls.bufferTimer);
- });
- hls.on(Hls.Events.BUFFER_RESET, function () {
- clearInterval(hls.bufferTimer);
- });
- hls.on(Hls.Events.FRAG_PARSING_INIT_SEGMENT, function (eventName, data) {
- showCanvas();
- events.video.push({
- time: self.performance.now() - events.t0,
- type: data.id + ' init segment',
- });
- trimEventHistory();
- });
- hls.on(Hls.Events.FRAG_PARSING_METADATA, function (eventName, data) {
- // console.log("Id3 samples ", data.samples);
- });
- hls.on(Hls.Events.LEVEL_SWITCHING, function (eventName, data) {
- events.level.push({
- time: self.performance.now() - events.t0,
- id: data.level,
- bitrate: Math.round(hls.levels[data.level].bitrate / 1000),
- });
- trimEventHistory();
- updateLevelInfo();
- });
- hls.on(Hls.Events.MANIFEST_PARSED, function (eventName, data) {
- events.load.push({
- type: 'manifest',
- name: '',
- start: 0,
- end: data.levels.length,
- time: data.stats.loading.start - events.t0,
- latency: data.stats.loading.first - data.stats.loading.start,
- load: data.stats.loading.end - data.stats.loading.first,
- duration: data.stats.loading.end - data.stats.loading.first,
- });
- trimEventHistory();
- self.refreshCanvas();
- });
- hls.on(Hls.Events.MANIFEST_PARSED, function (eventName, data) {
- logStatus(`${hls.levels.length} quality levels found`);
- logStatus('Manifest successfully loaded');
- stats = {
- levelNb: data.levels.length,
- levelParsed: 0,
- };
- trimEventHistory();
- updateLevelInfo();
- });
- hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, function (eventName, data) {
- logStatus('No of audio tracks found: ' + data.audioTracks.length);
- updateAudioTrackInfo();
- });
- hls.on(Hls.Events.AUDIO_TRACK_SWITCHING, function (eventName, data) {
- logStatus('Audio track switching...');
- updateAudioTrackInfo();
- events.video.push({
- time: self.performance.now() - events.t0,
- type: 'audio switching',
- name: '@' + data.id,
- });
- trimEventHistory();
- lastAudioTrackSwitchingIdx = events.video.length - 1;
- });
- hls.on(Hls.Events.AUDIO_TRACK_SWITCHED, function (eventName, data) {
- logStatus('Audio track switched');
- updateAudioTrackInfo();
- const event = {
- time: self.performance.now() - events.t0,
- type: 'audio switched',
- name: '@' + data.id,
- };
- if (lastAudioTrackSwitchingIdx !== undefined) {
- events.video[lastAudioTrackSwitchingIdx].duration =
- event.time - events.video[lastAudioTrackSwitchingIdx].time;
- lastAudioTrackSwitchingIdx = undefined;
- }
- events.video.push(event);
- trimEventHistory();
- });
- hls.on(Hls.Events.LEVEL_LOADED, function (eventName, data) {
- events.isLive = data.details.live;
- const event = {
- type: 'level',
- id: data.level,
- start: data.details.startSN,
- end: data.details.endSN,
- time: data.stats.loading.start - events.t0,
- latency: data.stats.loading.first - data.stats.loading.start,
- load: data.stats.loading.end - data.stats.loading.first,
- parsing: data.stats.parsing.end - data.stats.loading.end,
- duration: data.stats.loading.end - data.stats.loading.first,
- };
- const parsingDuration = data.stats.parsing.end - data.stats.loading.end;
- if (stats.levelParsed) {
- this.sumLevelParsingMs += parsingDuration;
- } else {
- this.sumLevelParsingMs = parsingDuration;
- }
- stats.levelParsed++;
- stats.levelParsingUs = Math.round(
- (1000 * this.sumLevelParsingMs) / stats.levelParsed
- );
- // console.log('parsing level duration :' + stats.levelParsingUs + 'us,count:' + stats.levelParsed);
- events.load.push(event);
- trimEventHistory();
- self.refreshCanvas();
- });
- hls.on(Hls.Events.AUDIO_TRACK_LOADED, function (eventName, data) {
- events.isLive = data.details.live;
- const event = {
- type: 'audio track',
- id: data.id,
- start: data.details.startSN,
- end: data.details.endSN,
- time: data.stats.loading.start - events.t0,
- latency: data.stats.loading.first - data.stats.loading.start,
- load: data.stats.loading.end - data.stats.loading.first,
- parsing: data.stats.parsing.end - data.stats.loading.end,
- duration: data.stats.loading.end - data.stats.loading.first,
- };
- events.load.push(event);
- trimEventHistory();
- self.refreshCanvas();
- });
- hls.on(Hls.Events.FRAG_BUFFERED, function (eventName, data) {
- const event = {
- type: data.frag.type + (data.part ? ' part' : ' fragment'),
- id: data.frag.level,
- id2: data.frag.sn,
- id3: data.part ? data.part.index : undefined,
- time: data.stats.loading.start - events.t0,
- latency: data.stats.loading.first - data.stats.loading.start,
- load: data.stats.loading.end - data.stats.loading.first,
- parsing: data.stats.parsing.end - data.stats.loading.end,
- buffer: data.stats.buffering.end - data.stats.parsing.end,
- duration: data.stats.buffering.end - data.stats.loading.first,
- bw: Math.round(
- (8 * data.stats.total) /
- (data.stats.buffering.end - data.stats.loading.start)
- ),
- size: data.stats.total,
- };
- events.load.push(event);
- events.bitrate.push({
- time: self.performance.now() - events.t0,
- bitrate: event.bw,
- duration: data.frag.duration,
- level: event.id,
- });
- if (events.buffer.length === 0) {
- events.buffer.push({
- time: 0,
- buffer: 0,
- pos: 0,
- });
- }
- clearInterval(hls.bufferTimer);
- hls.bufferTimer = self.setInterval(checkBuffer, 100);
- trimEventHistory();
- self.refreshCanvas();
- updateLevelInfo();
- const latency = data.stats.loading.first - data.stats.loading.start;
- const parsing = data.stats.parsing.end - data.stats.loading.end;
- const process = data.stats.buffering.end - data.stats.loading.start;
- const bitrate = Math.round(
- (8 * data.stats.total) /
- (data.stats.buffering.end - data.stats.loading.first)
- );
- if (stats.fragBuffered) {
- stats.fragMinLatency = Math.min(stats.fragMinLatency, latency);
- stats.fragMaxLatency = Math.max(stats.fragMaxLatency, latency);
- stats.fragMinProcess = Math.min(stats.fragMinProcess, process);
- stats.fragMaxProcess = Math.max(stats.fragMaxProcess, process);
- stats.fragMinKbps = Math.min(stats.fragMinKbps, bitrate);
- stats.fragMaxKbps = Math.max(stats.fragMaxKbps, bitrate);
- stats.autoLevelCappingMin = Math.min(
- stats.autoLevelCappingMin,
- hls.autoLevelCapping
- );
- stats.autoLevelCappingMax = Math.max(
- stats.autoLevelCappingMax,
- hls.autoLevelCapping
- );
- stats.fragBuffered++;
- } else {
- stats.fragMinLatency = stats.fragMaxLatency = latency;
- stats.fragMinProcess = stats.fragMaxProcess = process;
- stats.fragMinKbps = stats.fragMaxKbps = bitrate;
- stats.fragBuffered = 1;
- stats.fragBufferedBytes = 0;
- stats.autoLevelCappingMin = stats.autoLevelCappingMax =
- hls.autoLevelCapping;
- this.sumLatency = 0;
- this.sumKbps = 0;
- this.sumProcess = 0;
- this.sumParsing = 0;
- }
- stats.fraglastLatency = latency;
- this.sumLatency += latency;
- stats.fragAvgLatency = Math.round(this.sumLatency / stats.fragBuffered);
- stats.fragLastProcess = process;
- this.sumProcess += process;
- this.sumParsing += parsing;
- stats.fragAvgProcess = Math.round(this.sumProcess / stats.fragBuffered);
- stats.fragLastKbps = bitrate;
- this.sumKbps += bitrate;
- stats.fragAvgKbps = Math.round(this.sumKbps / stats.fragBuffered);
- stats.fragBufferedBytes += data.stats.total;
- stats.fragparsingKbps = Math.round(
- (8 * stats.fragBufferedBytes) / this.sumParsing
- );
- stats.fragparsingMs = Math.round(this.sumParsing);
- stats.autoLevelCappingLast = hls.autoLevelCapping;
- });
- hls.on(Hls.Events.LEVEL_SWITCHED, function (eventName, data) {
- const event = {
- time: self.performance.now() - events.t0,
- type: 'level switched',
- name: data.level,
- };
- events.video.push(event);
- trimEventHistory();
- self.refreshCanvas();
- updateLevelInfo();
- });
- hls.on(Hls.Events.FRAG_CHANGED, function (eventName, data) {
- const event = {
- time: self.performance.now() - events.t0,
- type: 'frag changed',
- name: data.frag.sn + ' @ ' + data.frag.level,
- };
- events.video.push(event);
- trimEventHistory();
- self.refreshCanvas();
- updateLevelInfo();
- stats.tagList = data.frag.tagList;
- const level = data.frag.level;
- const autoLevel = hls.autoLevelEnabled;
- if (stats.levelStart === undefined) {
- stats.levelStart = level;
- }
- stats.fragProgramDateTime = data.frag.programDateTime;
- stats.fragStart = data.frag.start;
- if (autoLevel) {
- if (stats.fragChangedAuto) {
- stats.autoLevelMin = Math.min(stats.autoLevelMin, level);
- stats.autoLevelMax = Math.max(stats.autoLevelMax, level);
- stats.fragChangedAuto++;
- if (this.levelLastAuto && level !== stats.autoLevelLast) {
- stats.autoLevelSwitch++;
- }
- } else {
- stats.autoLevelMin = stats.autoLevelMax = level;
- stats.autoLevelSwitch = 0;
- stats.fragChangedAuto = 1;
- this.sumAutoLevel = 0;
- }
- this.sumAutoLevel += level;
- stats.autoLevelAvg =
- Math.round((1000 * this.sumAutoLevel) / stats.fragChangedAuto) / 1000;
- stats.autoLevelLast = level;
- } else {
- if (stats.fragChangedManual) {
- stats.manualLevelMin = Math.min(stats.manualLevelMin, level);
- stats.manualLevelMax = Math.max(stats.manualLevelMax, level);
- stats.fragChangedManual++;
- if (!this.levelLastAuto && level !== stats.manualLevelLast) {
- stats.manualLevelSwitch++;
- }
- } else {
- stats.manualLevelMin = stats.manualLevelMax = level;
- stats.manualLevelSwitch = 0;
- stats.fragChangedManual = 1;
- }
- stats.manualLevelLast = level;
- }
- this.levelLastAuto = autoLevel;
- });
- hls.on(Hls.Events.FRAG_LOAD_EMERGENCY_ABORTED, function (eventName, data) {
- if (stats) {
- if (stats.fragLoadEmergencyAborted === undefined) {
- stats.fragLoadEmergencyAborted = 1;
- } else {
- stats.fragLoadEmergencyAborted++;
- }
- }
- });
- hls.on(Hls.Events.FRAG_DECRYPTED, function (eventName, data) {
- if (!stats.fragDecrypted) {
- stats.fragDecrypted = 0;
- this.totalDecryptTime = 0;
- stats.fragAvgDecryptTime = 0;
- }
- stats.fragDecrypted++;
- this.totalDecryptTime += data.stats.tdecrypt - data.stats.tstart;
- stats.fragAvgDecryptTime = this.totalDecryptTime / stats.fragDecrypted;
- });
- hls.on(Hls.Events.ERROR, function (eventName, data) {
- console.warn('Error event:', data);
- switch (data.details) {
- case Hls.ErrorDetails.MANIFEST_LOAD_ERROR:
- try {
- $('#errorOut').html(
- 'Cannot load <a href="' +
- data.context.url +
- '">' +
- url +
- '</a><br>HTTP response code:' +
- data.response.code +
- ' <br>' +
- data.response.text
- );
- if (data.response.code === 0) {
- $('#errorOut').append(
- 'This might be a CORS issue, consider installing <a href="https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi">Allow-Control-Allow-Origin</a> Chrome Extension'
- );
- }
- } catch (err) {
- $('#errorOut').html(
- 'Cannot load <a href="' +
- data.context.url +
- '">' +
- url +
- '</a><br>Response body: ' +
- data.response.text
- );
- }
- break;
- case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
- logError('Timeout while loading manifest');
- break;
- case Hls.ErrorDetails.MANIFEST_PARSING_ERROR:
- logError('Error while parsing manifest:' + data.reason);
- break;
- case Hls.ErrorDetails.LEVEL_EMPTY_ERROR:
- logError(
- 'Loaded level contains no fragments ' + data.level + ' ' + data.url
- );
- // handleLevelError demonstrates how to remove a level that errors followed by a downswitch
- // handleLevelError(data);
- break;
- case Hls.ErrorDetails.LEVEL_LOAD_ERROR:
- logError(
- 'Error while loading level playlist ' +
- data.context.level +
- ' ' +
- data.url
- );
- // handleLevelError demonstrates how to remove a level that errors followed by a downswitch
- // handleLevelError(data);
- break;
- case Hls.ErrorDetails.LEVEL_LOAD_TIMEOUT:
- logError(
- 'Timeout while loading level playlist ' +
- data.context.level +
- ' ' +
- data.url
- );
- // handleLevelError demonstrates how to remove a level that errors followed by a downswitch
- // handleLevelError(data);
- break;
- case Hls.ErrorDetails.LEVEL_SWITCH_ERROR:
- logError('Error while trying to switch to level ' + data.level);
- break;
- case Hls.ErrorDetails.FRAG_LOAD_ERROR:
- logError('Error while loading fragment ' + data.frag.url);
- break;
- case Hls.ErrorDetails.FRAG_LOAD_TIMEOUT:
- logError('Timeout while loading fragment ' + data.frag.url);
- break;
- case Hls.ErrorDetails.FRAG_LOOP_LOADING_ERROR:
- logError('Fragment-loop loading error');
- break;
- case Hls.ErrorDetails.FRAG_DECRYPT_ERROR:
- logError('Decrypting error:' + data.reason);
- break;
- case Hls.ErrorDetails.FRAG_PARSING_ERROR:
- logError('Parsing error:' + data.reason);
- break;
- case Hls.ErrorDetails.KEY_LOAD_ERROR:
- logError('Error while loading key ' + data.frag.decryptdata.uri);
- break;
- case Hls.ErrorDetails.KEY_LOAD_TIMEOUT:
- logError('Timeout while loading key ' + data.frag.decryptdata.uri);
- break;
- case Hls.ErrorDetails.BUFFER_APPEND_ERROR:
- logError('Buffer append error');
- break;
- case Hls.ErrorDetails.BUFFER_ADD_CODEC_ERROR:
- logError(
- 'Buffer add codec error for ' +
- data.mimeType +
- ':' +
- data.error.message
- );
- break;
- case Hls.ErrorDetails.BUFFER_APPENDING_ERROR:
- logError('Buffer appending error');
- break;
- case Hls.ErrorDetails.BUFFER_STALLED_ERROR:
- logError('Buffer stalled error');
- if (stopOnStall) {
- hls.stopLoad();
- video.pause();
- }
- break;
- default:
- break;
- }
- if (data.fatal) {
- console.error(`Fatal error : ${data.details}`);
- switch (data.type) {
- case Hls.ErrorTypes.MEDIA_ERROR:
- logError(`A media error occurred: ${data.details}`);
- handleMediaError();
- break;
- case Hls.ErrorTypes.NETWORK_ERROR:
- logError(`A network error occurred: ${data.details}`);
- break;
- default:
- logError(`An unrecoverable error occurred: ${data.details}`);
- hls.destroy();
- break;
- }
- }
- if (!stats) {
- stats = {};
- }
- // track all errors independently
- if (stats[data.details] === undefined) {
- stats[data.details] = 1;
- } else {
- stats[data.details] += 1;
- }
- // track fatal error
- if (data.fatal) {
- if (stats.fatalError === undefined) {
- stats.fatalError = 1;
- } else {
- stats.fatalError += 1;
- }
- }
- $('#statisticsOut').text(JSON.stringify(sortObject(stats), null, '\t'));
- });
- hls.on(Hls.Events.BUFFER_CREATED, function (eventName, data) {
- tracks = data.tracks;
- });
- hls.on(Hls.Events.BUFFER_APPENDING, function (eventName, data) {
- if (dumpfMP4) {
- fmp4Data[data.type].push(data.data);
- }
- });
- hls.on(Hls.Events.FPS_DROP, function (eventName, data) {
- const event = {
- time: self.performance.now() - events.t0,
- type: 'frame drop',
- name: data.currentDropped + '/' + data.currentDecoded,
- };
- events.video.push(event);
- trimEventHistory();
- if (stats) {
- if (stats.fpsDropEvent === undefined) {
- stats.fpsDropEvent = 1;
- } else {
- stats.fpsDropEvent++;
- }
- stats.fpsTotalDroppedFrames = data.totalDroppedFrames;
- }
- });
- }
- function addVideoEventListeners(video) {
- video.removeEventListener('resize', handleVideoEvent);
- video.removeEventListener('seeking', handleVideoEvent);
- video.removeEventListener('seeked', handleVideoEvent);
- video.removeEventListener('pause', handleVideoEvent);
- video.removeEventListener('play', handleVideoEvent);
- video.removeEventListener('canplay', handleVideoEvent);
- video.removeEventListener('canplaythrough', handleVideoEvent);
- video.removeEventListener('ended', handleVideoEvent);
- video.removeEventListener('playing', handleVideoEvent);
- video.removeEventListener('error', handleVideoEvent);
- video.removeEventListener('loadedmetadata', handleVideoEvent);
- video.removeEventListener('loadeddata', handleVideoEvent);
- video.removeEventListener('durationchange', handleVideoEvent);
- video.removeEventListener('volumechange', handleVolumeEvent);
- video.addEventListener('resize', handleVideoEvent);
- video.addEventListener('seeking', handleVideoEvent);
- video.addEventListener('seeked', handleVideoEvent);
- video.addEventListener('pause', handleVideoEvent);
- video.addEventListener('play', handleVideoEvent);
- video.addEventListener('canplay', handleVideoEvent);
- video.addEventListener('canplaythrough', handleVideoEvent);
- video.addEventListener('ended', handleVideoEvent);
- video.addEventListener('playing', handleVideoEvent);
- video.addEventListener('error', handleVideoEvent);
- video.addEventListener('loadedmetadata', handleVideoEvent);
- video.addEventListener('loadeddata', handleVideoEvent);
- video.addEventListener('durationchange', handleVideoEvent);
- video.addEventListener('volumechange', handleVolumeEvent);
- }
- function handleUnsupported() {
- if (navigator.userAgent.toLowerCase().indexOf('firefox') !== -1) {
- logStatus(
- 'You are using Firefox, it looks like MediaSource is not enabled,<br>please ensure the following keys are set appropriately in <b>about:config</b><br>media.mediasource.enabled=true<br>media.mediasource.mp4.enabled=true<br><b>media.mediasource.whitelist=false</b>'
- );
- } else {
- logStatus(
- 'Your Browser does not support MediaSourceExtension / MP4 mediasource'
- );
- }
- }
- function handleVideoEvent(evt) {
- let data = '';
- switch (evt.type) {
- case 'durationchange':
- if (evt.target.duration - lastDuration <= 0.5) {
- // some browsers report several duration change events with almost the same value ... avoid spamming video events
- return;
- }
- lastDuration = evt.target.duration;
- data = Math.round(evt.target.duration * 1000);
- break;
- case 'resize':
- data = evt.target.videoWidth + '/' + evt.target.videoHeight;
- playerResize();
- break;
- case 'loadedmetadata':
- case 'loadeddata':
- case 'canplay':
- case 'canplaythrough':
- case 'ended':
- case 'seeking':
- case 'seeked':
- case 'play':
- case 'playing':
- lastStartPosition = evt.target.currentTime;
- case 'pause':
- case 'waiting':
- case 'stalled':
- case 'error':
- data = Math.round(evt.target.currentTime * 1000);
- if (evt.type === 'error') {
- let errorTxt;
- const mediaError = evt.currentTarget.error;
- switch (mediaError.code) {
- case mediaError.MEDIA_ERR_ABORTED:
- errorTxt = 'You aborted the video playback';
- break;
- case mediaError.MEDIA_ERR_DECODE:
- errorTxt =
- 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support';
- handleMediaError();
- break;
- case mediaError.MEDIA_ERR_NETWORK:
- errorTxt =
- 'A network error caused the video download to fail part-way';
- break;
- case mediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
- errorTxt =
- 'The video could not be loaded, either because the server or network failed or because the format is not supported';
- break;
- }
- if (mediaError.message) {
- errorTxt += ' - ' + mediaError.message;
- }
- logStatus(errorTxt);
- console.error(errorTxt);
- }
- break;
- default:
- break;
- }
- const event = {
- time: self.performance.now() - events.t0,
- type: evt.type,
- name: data,
- };
- events.video.push(event);
- if (evt.type === 'seeking') {
- lastSeekingIdx = events.video.length - 1;
- }
- if (evt.type === 'seeked') {
- events.video[lastSeekingIdx].duration =
- event.time - events.video[lastSeekingIdx].time;
- }
- trimEventHistory();
- }
- function handleVolumeEvent() {
- localStorage.setItem(
- STORAGE_KEYS.volume,
- JSON.stringify({
- muted: video.muted,
- volume: video.volume,
- })
- );
- }
- function handleLevelError(data) {
- var levelObj = data.context || data;
- hls.removeLevel(levelObj.level, levelObj.urlId || 0);
- if (!hls.levels.length) {
- logError('All levels have been removed');
- hls.destroy();
- return;
- }
- // Trigger an immediate downswitch to the first level
- // This is to handle the case where we start at an empty level, where switching to auto causes hlsjs to stall
- hls.currentLevel = 0;
- // Set the quality back to auto so that we return to optimal quality
- hls.currentLevel = -1;
- }
- function handleMediaError() {
- if (autoRecoverError) {
- const now = self.performance.now();
- if (
- !self.recoverDecodingErrorDate ||
- now - self.recoverDecodingErrorDate > 3000
- ) {
- self.recoverDecodingErrorDate = self.performance.now();
- $('#statusOut').append(', trying to recover media error.');
- hls.recoverMediaError();
- } else {
- if (
- !self.recoverSwapAudioCodecDate ||
- now - self.recoverSwapAudioCodecDate > 3000
- ) {
- self.recoverSwapAudioCodecDate = self.performance.now();
- $('#statusOut').append(
- ', trying to swap audio codec and recover media error.'
- );
- hls.swapAudioCodec();
- hls.recoverMediaError();
- } else {
- $('#statusOut').append(
- ', cannot recover. Last media error recovery failed.'
- );
- }
- }
- }
- }
- function timeRangesToString(r) {
- let log = '';
- for (let i = 0; i < r.length; i++) {
- log += '[' + r.start(i) + ', ' + r.end(i) + ']';
- log += ' ';
- }
- return log;
- }
- function checkBuffer() {
- const canvas = document.querySelector('#bufferedCanvas');
- const ctx = canvas.getContext('2d');
- const r = video.buffered;
- const seekableEnd = getSeekableEnd();
- let bufferingDuration;
- if (r) {
- ctx.fillStyle = 'black';
- if (!canvas.width || canvas.width !== video.clientWidth) {
- canvas.width = video.clientWidth;
- }
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- const pos = video.currentTime;
- let bufferLen = 0;
- ctx.fillStyle = 'gray';
- for (let i = 0; i < r.length; i++) {
- const start = (r.start(i) / seekableEnd) * canvas.width;
- const end = (r.end(i) / seekableEnd) * canvas.width;
- ctx.fillRect(start, 2, Math.max(2, end - start), 11);
- if (pos >= r.start(i) && pos < r.end(i)) {
- // play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length
- bufferLen = r.end(i) - pos;
- }
- }
- // check if we are in buffering / or playback ended state
- if (
- bufferLen <= 0.1 &&
- video.paused === false &&
- pos - lastStartPosition > 0.5
- ) {
- if (lastDuration - pos <= 0.5 && events.isLive === false) {
- // don't create buffering event if we are at the end of the playlist, don't report ended for live playlist
- } else {
- // we are not at the end of the playlist ... real buffering
- if (bufferingIdx !== -1) {
- bufferingDuration =
- self.performance.now() -
- events.t0 -
- events.video[bufferingIdx].time;
- events.video[bufferingIdx].duration = bufferingDuration;
- events.video[bufferingIdx].name = bufferingDuration;
- } else {
- events.video.push({
- type: 'buffering',
- time: self.performance.now() - events.t0,
- });
- trimEventHistory();
- // we are in buffering state
- bufferingIdx = events.video.length - 1;
- }
- }
- }
- if (bufferLen > 0.1 && bufferingIdx !== -1) {
- bufferingDuration =
- self.performance.now() - events.t0 - events.video[bufferingIdx].time;
- events.video[bufferingIdx].duration = bufferingDuration;
- events.video[bufferingIdx].name = bufferingDuration;
- // we are out of buffering state
- bufferingIdx = -1;
- }
- // update buffer/position for current Time
- const event = {
- time: self.performance.now() - events.t0,
- buffer: Math.round(bufferLen * 1000),
- pos: Math.round(pos * 1000),
- };
- const bufEvents = events.buffer;
- const bufEventLen = bufEvents.length;
- if (bufEventLen > 1) {
- const event0 = bufEvents[bufEventLen - 2];
- const event1 = bufEvents[bufEventLen - 1];
- const slopeBuf0 =
- (event0.buffer - event1.buffer) / (event0.time - event1.time);
- const slopeBuf1 =
- (event1.buffer - event.buffer) / (event1.time - event.time);
- const slopePos0 = (event0.pos - event1.pos) / (event0.time - event1.time);
- const slopePos1 = (event1.pos - event.pos) / (event1.time - event.time);
- // compute slopes. if less than 30% difference, remove event1
- if (
- (slopeBuf0 === slopeBuf1 ||
- Math.abs(slopeBuf0 / slopeBuf1 - 1) <= 0.3) &&
- (slopePos0 === slopePos1 || Math.abs(slopePos0 / slopePos1 - 1) <= 0.3)
- ) {
- bufEvents.pop();
- }
- }
- events.buffer.push(event);
- trimEventHistory();
- self.refreshCanvas();
- if ($('#statsDisplayTab').is(':visible')) {
- let log = `Duration: ${video.duration}\nBuffered: ${timeRangesToString(
- video.buffered
- )}\nSeekable: ${timeRangesToString(
- video.seekable
- )}\nPlayed: ${timeRangesToString(video.played)}\n`;
- if (hls.media) {
- for (const type in tracks) {
- log += `Buffer for ${type} contains:${timeRangesToString(
- tracks[type].buffer.buffered
- )}\n`;
- }
- const videoPlaybackQuality = video.getVideoPlaybackQuality;
- if (
- videoPlaybackQuality &&
- typeof videoPlaybackQuality === typeof Function
- ) {
- log += `Dropped frames: ${
- video.getVideoPlaybackQuality().droppedVideoFrames
- }\n`;
- log += `Corrupted frames: ${
- video.getVideoPlaybackQuality().corruptedVideoFrames
- }\n`;
- } else if (video.webkitDroppedFrameCount) {
- log += `Dropped frames: ${video.webkitDroppedFrameCount}\n`;
- }
- }
- log += `Bandwidth Estimate: ${hls.bandwidthEstimate.toFixed(3)}\n`;
- if (events.isLive) {
- log +=
- 'Live Stats:\n' +
- ` Max Latency: ${hls.maxLatency}\n` +
- ` Target Latency: ${hls.targetLatency.toFixed(3)}\n` +
- ` Latency: ${hls.latency.toFixed(3)}\n` +
- ` Drift: ${hls.drift.toFixed(3)} (edge advance rate)\n` +
- ` Edge Stall: ${hls.latencyController.edgeStalled.toFixed(
- 3
- )} (playlist refresh over target duration/part)\n` +
- ` Playback rate: ${video.playbackRate.toFixed(2)}\n`;
- if (stats.fragProgramDateTime) {
- const currentPDT =
- stats.fragProgramDateTime +
- (video.currentTime - stats.fragStart) * 1000;
- log += ` Program Date Time: ${new Date(currentPDT).toISOString()}`;
- const pdtLatency = (Date.now() - currentPDT) / 1000;
- if (pdtLatency > 0) {
- log += ` (${pdtLatency.toFixed(3)} seconds ago)`;
- }
- }
- }
- $('#bufferedOut').text(log);
- $('#statisticsOut').text(JSON.stringify(sortObject(stats), null, '\t'));
- }
- ctx.fillStyle = 'blue';
- const x = (video.currentTime / seekableEnd) * canvas.width;
- ctx.fillRect(x, 0, 2, 15);
- } else if (ctx.fillStyle !== 'black') {
- ctx.fillStyle = 'black';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- }
- }
- function showCanvas() {
- self.showMetrics();
- $('#bufferedOut').show();
- $('#bufferedCanvas').show();
- }
- function hideCanvas() {
- self.hideMetrics();
- $('#bufferedOut').hide();
- $('#bufferedCanvas').hide();
- }
- function getMetrics() {
- const json = JSON.stringify(events);
- const jsonpacked = pack(json);
- // console.log('packing JSON from ' + json.length + ' to ' + jsonpacked.length + ' bytes');
- return btoa(jsonpacked);
- }
- self.copyMetricsToClipBoard = function () {
- copyTextToClipboard(getMetrics());
- };
- self.goToMetrics = function () {
- let url = document.URL;
- url = url.slice(0, url.lastIndexOf('/') + 1) + 'metrics.html';
- self.open(url, '_blank');
- };
- function goToMetricsPermaLink() {
- let url = document.URL;
- const b64 = getMetrics();
- url = url.slice(0, url.lastIndexOf('/') + 1) + 'metrics.html#data=' + b64;
- self.open(url, '_blank');
- }
- function onClickBufferedRange(event) {
- const canvas = document.querySelector('#bufferedCanvas');
- const target =
- ((event.clientX - canvas.offsetLeft) / canvas.width) * getSeekableEnd();
- video.currentTime = target;
- }
- function getSeekableEnd() {
- if (isFinite(video.duration)) {
- return video.duration;
- }
- if (video.seekable.length) {
- return video.seekable.end(video.seekable.length - 1);
- }
- return 0;
- }
- function getLevelButtonHtml(key, levels, onclickReplace, autoEnabled) {
- const onclickAuto = `${key}=-1`.replace(/^(\w+)=([^=]+)$/, onclickReplace);
- const codecs = levels.reduce((uniqueCodecs, level) => {
- const levelCodecs = codecs2label(level.attrs.CODECS);
- if (levelCodecs && uniqueCodecs.indexOf(levelCodecs) === -1) {
- uniqueCodecs.push(levelCodecs);
- }
- return uniqueCodecs;
- }, []);
- return (
- `<button type="button" class="btn btn-sm ${
- autoEnabled ? 'btn-primary' : 'btn-success'
- }" onclick="${onclickAuto}">auto</button>` +
- levels
- .map((level, i) => {
- const enabled = hls[key] === i;
- const onclick = `${key}=${i}`.replace(/^(\w+)=(\w+)$/, onclickReplace);
- const label = level2label(levels[i], i, codecs);
- return `<button type="button" class="btn btn-sm ${
- enabled ? 'btn-primary' : 'btn-success'
- }" onclick="${onclick}">${label}</button>`;
- })
- .join('')
- );
- }
- function updateLevelInfo() {
- const levels = hls.levels;
- if (!levels) {
- return;
- }
- const htmlCurrentLevel = getLevelButtonHtml(
- 'currentLevel',
- levels,
- 'hls.$1=$2',
- hls.autoLevelEnabled
- );
- const htmlNextLevel = getLevelButtonHtml(
- 'nextLevel',
- levels,
- 'hls.$1=$2',
- hls.autoLevelEnabled
- );
- const htmlLoadLevel = getLevelButtonHtml(
- 'loadLevel',
- levels,
- 'hls.$1=$2',
- hls.autoLevelEnabled
- );
- const htmlCapLevel = getLevelButtonHtml(
- 'autoLevelCapping',
- levels,
- 'levelCapping=hls.$1=$2;updateLevelInfo();onDemoConfigChanged();',
- hls.autoLevelCapping === -1
- );
- if ($('#currentLevelControl').html() !== htmlCurrentLevel) {
- $('#currentLevelControl').html(htmlCurrentLevel);
- }
- if ($('#nextLevelControl').html() !== htmlNextLevel) {
- $('#nextLevelControl').html(htmlNextLevel);
- }
- if ($('#loadLevelControl').html() !== htmlLoadLevel) {
- $('#loadLevelControl').html(htmlLoadLevel);
- }
- if ($('#levelCappingControl').html() !== htmlCapLevel) {
- $('#levelCappingControl').html(htmlCapLevel);
- }
- }
- function updateAudioTrackInfo() {
- const buttonTemplate = '<button type="button" class="btn btn-sm ';
- const buttonEnabled = 'btn-primary" ';
- const buttonDisabled = 'btn-success" ';
- let html1 = '';
- const audioTrackId = hls.audioTrack;
- const len = hls.audioTracks.length;
- const track = hls.audioTracks[audioTrackId];
- for (let i = 0; i < len; i++) {
- html1 += buttonTemplate;
- if (audioTrackId === i) {
- html1 += buttonEnabled;
- } else {
- html1 += buttonDisabled;
- }
- html1 +=
- 'onclick="hls.audioTrack=' +
- i +
- '">' +
- hls.audioTracks[i].name +
- '</button>';
- }
- $('#audioTrackLabel').text(
- track ? track.lang || track.name : 'None selected'
- );
- $('#audioTrackControl').html(html1);
- }
- function codecs2label(levelCodecs) {
- if (levelCodecs) {
- return levelCodecs
- .replace(/([ah]vc.)[^,;]+/, '$1')
- .replace('mp4a.40.2', 'mp4a');
- }
- return '';
- }
- function level2label(level, i, manifestCodecs) {
- const levelCodecs = codecs2label(level.attrs.CODECS);
- const levelNameInfo = level.name ? `"${level.name}": ` : '';
- const codecInfo =
- levelCodecs && manifestCodecs.length > 1 ? ` / ${levelCodecs}` : '';
- if (level.height) {
- return `${i} (${levelNameInfo}${level.height}p / ${Math.round(
- level.bitrate / 1024
- )}kb${codecInfo})`;
- }
- if (level.bitrate) {
- return `${i} (${levelNameInfo}${Math.round(
- level.bitrate / 1024
- )}kb${codecInfo})`;
- }
- if (codecInfo) {
- return `${i} (${levelNameInfo}${levelCodecs})`;
- }
- if (level.name) {
- return `${i} (${level.name})`;
- }
- return `${i}`;
- }
- function getDemoConfigPropOrDefault(propName, defaultVal) {
- return typeof demoConfig[propName] !== 'undefined'
- ? demoConfig[propName]
- : defaultVal;
- }
- function getURLParam(sParam, defaultValue) {
- const sPageURL = self.location.search.substring(1);
- const sURLVariables = sPageURL.split('&');
- for (let i = 0; i < sURLVariables.length; i++) {
- const sParameterName = sURLVariables[i].split('=');
- if (sParameterName[0] === sParam) {
- return sParameterName[1] === 'undefined'
- ? undefined
- : sParameterName[1] === 'false'
- ? false
- : sParameterName[1];
- }
- }
- return defaultValue;
- }
- function onDemoConfigChanged(firstLoad) {
- demoConfig = {
- enableStreaming,
- autoRecoverError,
- stopOnStall,
- dumpfMP4,
- levelCapping,
- limitMetrics,
- };
- if (configPersistenceEnabled) {
- persistEditorValue();
- }
- const serializedDemoConfig = btoa(JSON.stringify(demoConfig));
- const baseURL = document.URL.split('?')[0];
- const streamURL = $('#streamURL').val();
- const permalinkURL = `${baseURL}?src=${encodeURIComponent(
- streamURL
- )}&demoConfig=${serializedDemoConfig}`;
- $('#StreamPermalink').html(`<a href="${permalinkURL}">${permalinkURL}</a>`);
- if (!firstLoad && window.location.href !== permalinkURL) {
- window.history.pushState(null, null, permalinkURL);
- }
- }
- function onConfigPersistenceChanged(event) {
- configPersistenceEnabled = event.target.checked;
- localStorage.setItem(
- STORAGE_KEYS.Editor_Persistence,
- JSON.stringify(configPersistenceEnabled)
- );
- if (configPersistenceEnabled) {
- persistEditorValue();
- } else {
- localStorage.removeItem(STORAGE_KEYS.Hls_Config);
- }
- }
- function getEditorValue(options) {
- options = $.extend({ parse: false }, options || {});
- let value = configEditor.session.getValue();
- if (options.parse) {
- try {
- value = JSON.parse(value);
- } catch (e) {
- console.warn('[getEditorValue] could not parse editor value', e);
- value = {};
- }
- }
- return value;
- }
- function getPersistedHlsConfig() {
- let value = localStorage.getItem(STORAGE_KEYS.Hls_Config);
- if (value === null) {
- return value;
- }
- try {
- value = JSON.parse(value);
- } catch (e) {
- console.warn('[getPersistedHlsConfig] could not hls config json', e);
- value = {};
- }
- return value;
- }
- function persistEditorValue() {
- localStorage.setItem(STORAGE_KEYS.Hls_Config, getEditorValue());
- }
- function setupConfigEditor() {
- configEditor = self.ace.edit('config-editor');
- configEditor.setTheme('ace/theme/github');
- configEditor.session.setMode('ace/mode/json');
- const contents = hlsjsDefaults;
- const shouldRestorePersisted =
- JSON.parse(localStorage.getItem(STORAGE_KEYS.Editor_Persistence)) === true;
- if (shouldRestorePersisted) {
- $.extend(contents, getPersistedHlsConfig());
- }
- const elPersistence = document.querySelector('#config-persistence');
- elPersistence.addEventListener('change', onConfigPersistenceChanged);
- elPersistence.checked = shouldRestorePersisted;
- configPersistenceEnabled = shouldRestorePersisted;
- updateConfigEditorValue(contents);
- }
- function setupTimelineChart() {
- const canvas = document.querySelector('#timeline-chart');
- const chart = new TimelineChart(canvas, {
- responsive: false,
- });
- resizeHandlers.push(() => {
- chart.resize();
- });
- chart.resize();
- return chart;
- }
- function addChartEventListeners(hls) {
- const updateLevelOrTrack = (eventName, data) => {
- chart.updateLevelOrTrack(data.details);
- };
- const updateFragment = (eventName, data) => {
- if (data.stats) {
- // Convert 0.x stats to partial v1 stats
- const { retry, loaded, total, trequest, tfirst, tload } = data.stats;
- if (trequest && tload) {
- data.frag.stats = {
- loaded,
- retry,
- total,
- loading: {
- start: trequest,
- first: tfirst,
- end: tload,
- },
- };
- }
- }
- chart.updateFragment(data);
- };
- const updateChart = () => {
- chart.update();
- };
- hls.on(
- Hls.Events.MANIFEST_LOADING,
- () => {
- chart.reset();
- },
- chart
- );
- hls.on(
- Hls.Events.MANIFEST_PARSED,
- (eventName, data) => {
- const { levels } = data;
- chart.removeType('level');
- chart.removeType('audioTrack');
- chart.removeType('subtitleTrack');
- chart.updateLevels(levels);
- },
- chart
- );
- hls.on(
- Hls.Events.BUFFER_CREATED,
- (eventName, { tracks }) => {
- chart.updateSourceBuffers(tracks, hls.media);
- },
- chart
- );
- hls.on(
- Hls.Events.BUFFER_RESET,
- () => {
- chart.removeSourceBuffers();
- },
- chart
- );
- hls.on(Hls.Events.LEVELS_UPDATED, (eventName, { levels }) => {
- chart.removeType('level');
- chart.updateLevels(levels);
- });
- hls.on(
- Hls.Events.LEVEL_SWITCHED,
- (eventName, { level }) => {
- chart.removeType('level');
- chart.updateLevels(hls.levels, level);
- },
- chart
- );
- hls.on(
- Hls.Events.LEVEL_LOADING,
- () => {
- // TODO: mutate level datasets
- // Update loadLevel
- chart.removeType('level');
- chart.updateLevels(hls.levels);
- },
- chart
- );
- hls.on(
- Hls.Events.LEVEL_UPDATED,
- (eventName, { details }) => {
- chart.updateLevelOrTrack(details);
- },
- chart
- );
- hls.on(
- Hls.Events.AUDIO_TRACKS_UPDATED,
- (eventName, { audioTracks }) => {
- chart.removeType('audioTrack');
- chart.updateAudioTracks(audioTracks);
- },
- chart
- );
- hls.on(
- Hls.Events.SUBTITLE_TRACKS_UPDATED,
- (eventName, { subtitleTracks }) => {
- chart.removeType('subtitleTrack');
- chart.updateSubtitleTracks(subtitleTracks);
- },
- chart
- );
- hls.on(
- Hls.Events.AUDIO_TRACK_SWITCHED,
- (eventName) => {
- // TODO: mutate level datasets
- chart.removeType('audioTrack');
- chart.updateAudioTracks(hls.audioTracks);
- },
- chart
- );
- hls.on(
- Hls.Events.SUBTITLE_TRACK_SWITCH,
- (eventName) => {
- // TODO: mutate level datasets
- chart.removeType('subtitleTrack');
- chart.updateSubtitleTracks(hls.subtitleTracks);
- },
- chart
- );
- hls.on(Hls.Events.AUDIO_TRACK_LOADED, updateLevelOrTrack, chart);
- hls.on(Hls.Events.SUBTITLE_TRACK_LOADED, updateLevelOrTrack, chart);
- hls.on(Hls.Events.LEVEL_PTS_UPDATED, updateLevelOrTrack, chart);
- hls.on(Hls.Events.FRAG_LOADED, updateFragment, chart);
- hls.on(Hls.Events.FRAG_PARSED, updateFragment, chart);
- hls.on(Hls.Events.FRAG_CHANGED, updateFragment, chart);
- hls.on(Hls.Events.BUFFER_APPENDING, updateChart, chart);
- hls.on(Hls.Events.BUFFER_APPENDED, updateChart, chart);
- hls.on(Hls.Events.BUFFER_FLUSHED, updateChart, chart);
- }
- function updateConfigEditorValue(obj) {
- const json = JSON.stringify(obj, null, 2);
- configEditor.session.setValue(json);
- }
- function applyConfigEditorValue() {
- onDemoConfigChanged();
- loadSelectedStream();
- }
- function createfMP4(type) {
- if (fmp4Data[type].length) {
- const blob = new Blob([arrayConcat(fmp4Data[type])], {
- type: 'application/octet-stream',
- });
- const filename = type + '-' + new Date().toISOString() + '.mp4';
- self.saveAs(blob, filename);
- // $('body').append('<a download="hlsjs-' + filename + '" href="' + self.URL.createObjectURL(blob) + '">Download ' + filename + ' track</a><br>');
- } else if (!dumpfMP4) {
- console.error(
- 'Check "Dump transmuxed fMP4 data" first to make appended media available for saving.'
- );
- }
- }
- function arrayConcat(inputArray) {
- const totalLength = inputArray.reduce(function (prev, cur) {
- return prev + cur.length;
- }, 0);
- const result = new Uint8Array(totalLength);
- let offset = 0;
- inputArray.forEach(function (element) {
- result.set(element, offset);
- offset += element.length;
- });
- return result;
- }
- function hideAllTabs() {
- $('.demo-tab-btn').css('background-color', '');
- $('.demo-tab').hide();
- }
- function toggleTabClick(btn) {
- toggleTab(btn);
- const tabIndexes = $('.demo-tab-btn')
- .toArray()
- .map((el, i) => ($('#' + $(el).data('tab')).is(':visible') ? i : null))
- .filter((i) => i !== null);
- localStorage.setItem(STORAGE_KEYS.demo_tabs, tabIndexes.join(','));
- }
- function toggleTab(btn, dontHideOpenTabs) {
- const tabElId = $(btn).data('tab');
- // eslint-disable-next-line no-restricted-globals
- const modifierPressed =
- dontHideOpenTabs ||
- (self.event && (self.event.metaKey || self.event.shiftKey));
- if (!modifierPressed) {
- hideAllTabs();
- }
- if (modifierPressed) {
- $(`#${tabElId}`).toggle();
- } else {
- $(`#${tabElId}`).show();
- }
- $(btn).css(
- 'background-color',
- $(`#${tabElId}`).is(':visible') ? 'orange' : ''
- );
- if (!$('#statsDisplayTab').is(':visible')) {
- self.hideMetrics();
- }
- if (hls) {
- if ($('#timelineTab').is(':visible')) {
- chart.show();
- chart.resize(chart.chart.data ? chart.chart.data.datasets : null);
- } else {
- chart.hide();
- }
- }
- }
- function appendLog(textElId, message) {
- const el = $('#' + textElId);
- let logText = el.text();
- if (logText.length) {
- logText += '\n';
- }
- const timestamp = (Date.now() - startTime) / 1000;
- const newMessage = timestamp + ' | ' + message;
- logText += newMessage;
- // update
- el.text(logText);
- const element = el[0];
- element.scrollTop = element.scrollHeight - element.clientHeight;
- }
- function logStatus(message) {
- appendLog('statusOut', message);
- }
- function logError(message) {
- appendLog('errorOut', message);
- }
|