You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

293 lines
8.9 KiB

(function(window, videojs, undefined) {
'use strict';
// -------------
// Initial Setup
// -------------
var d3 = window.d3;
var bitrateTickFormatter = d3.format(',.0f');
var updateBitrateAxes = function(svg, xScale, yScale) {
var xAxis = d3.svg.axis().scale(xScale).orient('bottom');
svg.select('.axis.x')
.transition().duration(500)
.call(xAxis);
var yAxis = d3.svg.axis().scale(yScale)
.tickFormat(function(value) {
return bitrateTickFormatter(value / 1024);
}).orient('left');
svg.select('.axis.y')
.transition().duration(500)
.call(yAxis);
};
var updateBitrates = function(svg, x, y, measuredBitrateKbps) {
var bitrates, line;
bitrates = svg.selectAll('.bitrates').datum(measuredBitrateKbps);
line = d3.svg.line()
.x(function(bitrate) { return x(bitrate.time); })
.y(function(bitrate) { return y(bitrate.value); });
bitrates.transition().duration(500).attr('d', line);
};
var setupGraph = function(element, player) {
// setup the display
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
};
var width = 600 - margin.left - margin.right;
var height = 300 - margin.top - margin.bottom;
var selected = d3.select(element);
// clear out the element
selected.selectAll('*').remove();
var svg = selected
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// setup the timeline
var x = d3.time.scale().range([0, width]); // d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
x.domain([new Date(), new Date(Date.now() + (1 * 60 * 1000))]);
y.domain([0, 5 * 1024 * 1024 * 8]);
var timeAxis = d3.svg.axis().scale(x).orient('bottom');
var bitrateAxis = d3.svg.axis()
.scale(y)
.tickFormat(function(value) {
return bitrateTickFormatter(value / 1024);
})
.orient('left');
// time axis
svg.selectAll('.axis').remove();
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(timeAxis);
// bitrate axis
svg.append('g')
.attr('class', 'y axis')
.call(bitrateAxis)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Bitrate (kb/s)');
svg.append('path')
.attr('class', 'bitrates');
var measuredBitrateKbps = [{
time: new Date(),
value: player.tech(true).vhs.bandwidth || 0
}];
player.on('progress', function() {
measuredBitrateKbps.push({
time: new Date(),
value: player.tech(true).vhs.bandwidth || 0
});
x.domain([x.domain()[0], new Date()]);
y.domain([0, d3.max(measuredBitrateKbps, function(bitrate) {
return bitrate.value;
})]);
updateBitrateAxes(svg, x, y);
updateBitrates(svg, x, y, measuredBitrateKbps);
});
};
// ---------------
// Dynamic Updates
// ---------------
var displayStats = function(element, player) {
setupGraph(element, player);
};
// -----------------
// Cue Visualization
// -----------------
var Playlist = videojs.Vhs.Playlist;
var margin = {
top: 8,
right: 8,
bottom: 20,
left: 80
};
var width = 600 - margin.left - margin.right;
var height = 600 - margin.top - margin.bottom;
var mediaDomain = function(media, player) {
var segments = media.segments;
var end = player.tech(true).vhs.playlists.expiredPreDiscontinuity_;
end += player.tech(true).vhs.playlists.expiredPostDiscontinuity_;
end += Playlist.duration(media,
media.mediaSequence,
media.mediaSequence + segments.length);
return [0, end];
};
var ptsDomain = function(segments, mediaScale, mediaOffset) {
mediaOffset = mediaOffset * 1000 || 0;
var start = mediaScale.domain()[0] * 1000;
var segment = segments[0];
if (segment &&
segment.minAudioPts !== undefined ||
segment.minVideoPts !== undefined) {
start = Math.min(segment.minAudioPts || Infinity,
segment.minVideoPts || Infinity);
}
start -= mediaOffset;
return [
start,
(mediaScale.domain()[1] - mediaScale.domain()[0]) * 1000 + start
];
};
var svgUpdateCues = function(svg, mediaScale, ptsScale, y, cues) {
cues = Array.prototype.slice.call(cues).filter(function(cue) {
return cue.startTime > mediaScale.domain()[0] &&
cue.startTime < mediaScale.domain()[1];
});
var points = svg.selectAll('.cue').data(cues, function(cue) {
return cue.pts_ + ' -> ' + cue.startTime;
});
points.attr('transform', function(cue) {
return 'translate(' + mediaScale(cue.startTime) + ',' + ptsScale(cue.pts_) + ')';
});
var enter = points.enter().append('g')
.attr('class', 'cue');
enter.append('circle')
.attr('r', 5)
.attr('data-time', function(cue) {
return cue.startTime;
})
.attr('data-pts', function(cue) {
return cue.pts_;
});
enter.append('text')
.attr('transform', 'translate(8,0)')
.text(function(cue) {
return 'time: ' + videojs.formatTime(cue.startTime);
});
enter.append('text')
.attr('transform', 'translate(8,16)')
.text(function(cue) {
return 'pts: ' + cue.pts_;
});
points.exit().remove();
};
var svgUpdateAxes = function(svg, mediaScale, ptsScale) {
// media timeline axis
var mediaAxis = d3.svg.axis().scale(mediaScale).orient('bottom');
svg.select('.axis.media')
.transition().duration(500)
.call(mediaAxis);
// presentation timeline axis
if (!isFinite(ptsScale.domain()[0]) || !isFinite(ptsScale.domain()[1])) {
return;
}
var ptsAxis = d3.svg.axis().scale(ptsScale).orient('left');
svg.select('.axis.presentation')
.transition().duration(500)
.call(ptsAxis);
};
var svgRenderSegmentTimeline = function(container, player) {
var media = player.tech(true).vhs.playlists.media();
var segments = media.segments; // media.segments.slice(0, count);
var selected = d3.select(container);
// clear out the container
selected.selectAll('*').remove();
// setup the display
var svg = selected
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// setup the scales
var mediaScale = d3.scale.linear().range([0, width]);
mediaScale.domain(mediaDomain(media, player));
var ptsScale = d3.scale.linear().range([height, 0]);
ptsScale.domain(ptsDomain(segments, mediaScale));
// render
var mediaAxis = d3.svg.axis().scale(mediaScale).orient('bottom');
svg.append('g')
.attr('class', 'x axis media')
.attr('transform', 'translate(0,' + height + ')')
.call(mediaAxis);
var ptsAxis = d3.svg.axis().scale(ptsScale).orient('left');
svg.append('g')
.attr('class', 'y axis presentation')
.call(ptsAxis);
svg.append('path')
.attr('class', 'intersect')
.attr('d', 'M0,' + height + 'L' + width +',0');
var mediaOffset = 0;
// update everything on progress
player.on('progress', function() {
var updatedMedia = player.tech(true).vhs.playlists.media();
var segments = updatedMedia.segments; // updatedMedia.segments.slice(currentIndex, currentIndex + count);
if (updatedMedia.mediaSequence !== media.mediaSequence) {
mediaOffset += Playlist.duration(media,
media.mediaSequence,
updatedMedia.mediaSequence);
media = updatedMedia;
}
mediaScale.domain(mediaDomain(updatedMedia, player));
ptsScale.domain(ptsDomain(segments, mediaScale, mediaOffset));
svgUpdateAxes(svg, mediaScale, ptsScale, updatedMedia, segments);
if (!isFinite(ptsScale.domain()[0]) || !isFinite(ptsScale.domain()[1])) {
return;
}
for (var i = 0; i < player.textTracks().length; i++) {
var track = player.textTracks()[i];
svgUpdateCues(svg, mediaScale, ptsScale, ptsScale, track.cues);
}
});
};
var displayCues = function(container, player) {
var media = player.tech(true).vhs.playlists.media();
if (media && media.segments) {
svgRenderSegmentTimeline(container, player);
} else {
player.one('loadedmetadata', function() {
svgRenderSegmentTimeline(container, player);
});
}
};
// export
videojs.Vhs.displayStats = displayStats;
videojs.Vhs.displayCues = displayCues;
})(window, window.videojs);