Videojs duration displaying as 0 for entire video - html5-video

I'm using videojs. For some reason the duration of videos is displaying as 0, even when fully loaded.
At line 2487 of the video.js file I've made sure this section...
ControlBar.prototype.options_ = {
children: ['playToggle', 'volumeMenuButton', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subtitlesButton', 'captionsButton', 'audioTrackButton', 'fullscreenToggle']
};
...includes the 'durationDisplay' property, so does anyone know why the duration is displaying as 0?
The videos are mp4 and are loaded inside an AngularJS directive:
app.directive('engVideo',['$timeout', '$http', function($timeout, $http) {
return {
restrict: 'A',
priority: 100,
replace: true,
templateUrl: 'components/video.html',
link: function(scope, element, attrs) {
....
function VideoJSPlayerInit(window, videojs) {
var player = videojs(scope.component.video.id, {
html5: {
nativeTextTracks: false
}
});
player.pause();
}
From a suggestion in the comments, I've also tried listening for the 'loadedmetadata' event, when the videojs element is created, like this:
function VideoJSPlayerInit(window, videojs) {
var player = videojs(scope.component.video.id, {
html5: {
nativeTextTracks: false
}
}, function() {
this.on('loadedmetadata', function(){
console.log("video metadata loaded");
});
}
);
But nothing gets output to console - so I'm guessing there's no metadata loaded(?) I have also changed it to listen for the 'loadeddata' event and that DOES gets consoled.
Could this be a video encoding issue? I've been looking for how to export from Premiere with the duration metadata included, but as far as I can tell, it's there.
Any clues, much appreciated.

OK, I've finally figured it out: It was not to do with metadata; The version of video.js we're using for some reason was hardcoding the duration value as '0:00'. If it's useful to anyone else, here's what I added (to the video.js file from line 5241) to get the duration to display correctly:
DurationDisplay.prototype.createEl = function createEl() {
var el = _Component.prototype.createEl.call(this, 'div', {
className: 'vjs-duration vjs-time-control vjs-control'
});
// following three lines are new...
var intSeconds = parseInt(this.player_.duration());
var intMinutes = parseInt(intSeconds / 60);
intSeconds = intSeconds - (60 * intMinutes);
this.contentEl_ = Dom.createEl('div', {
className: 'vjs-duration-display',
// label the duration time for screen reader users
//innerHTML: '<span class="vjs-control-text">' + this.localize('Duration Time') + '</span> 0:00' // - old line
innerHTML: '<span class="vjs-control-text">' + this.localize('Duration Time') + '</span>' + intMinutes + ':' + intSeconds
}, {
// tell screen readers not to automatically read the time as it changes
'aria-live': 'off'
});
el.appendChild(this.contentEl_);
return el;
};

Related

VueJS Leaflet 'moveend' fires multiple times

Ask for help from the community. For two weeks I can not overcome the problem with repeated firing of 'mooveend' in the project. I have tried all the advice given here. Here's what I've read and researched already, but it didn't work for me.
This is one of the tips:
moveend event fired many times when page is load with Leaflet
<template>
<div id="map"></div>
</template>
<script>
export default {
name: "ObjectMapView",
props: ['coordinate'],
data: function () {
return {
map: null,
addressPoints: null,
markers: null,
}
},
mounted: function() {
this.initializedMap();
},
watch: {
coordinate: function (val) {
this.run();
}
},
methods: {
initializedMap: function () {
this.map = L.map('map').setView([52.5073390000,5.4742833000], 13);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(this.map);
this.markers = L.markerClusterGroup();
},
run: function () {
var map = this.map;
var markers = this.markers;
var getAllObjects = this.coordinate;
var getBoundsMarkers;
//Clearing Layers When Switching a Filter
markers.clearLayers();
this.addressPoints = getAllObjects.map(function (latlng){
return [latlng.latitude, latlng.longitude, latlng.zip, latlng.object_id, latlng.archived];
});
map.addLayer(markers);
//We give to the map only those coordinates that are in the zone of visibility of the map during the first
getBoundsMarkers = getAllObjects.filter((coord) => {
if(!coord.latitude && !coord.longitude){
return false;
}
return map.getBounds().contains(L.latLng(coord.latitude, coord.longitude));
});
/*
Responds to changing the boundaries of the map visibility zone and
transmits a list of coordinates that are in the visibility zone
*/
console.log('getAllObjects_1', getAllObjects);
map.on('moveend', function() {
console.log('moveend');
console.log('getAllObjects_2', getAllObjects);
getBoundsMarkers = getAllObjects.filter((coord) => {
if(!coord.latitude && !coord.longitude){
return false;
}
return map.getBounds().contains(L.latLng(coord.latitude, coord.longitude));
});
eventHub.$emit('sendMarkers', getBoundsMarkers);
});
// In the loop, we iterate over the coordinates and give them to the map
for (var i = 0; i < this.addressPoints.length; i++) {
var a = this.addressPoints[i];
var title = '' + a[2] + ''; //bubble
var marker = L.marker(new L.LatLng(a[0], a[1]), {
title: title
});
marker.bindPopup(title);
markers.addLayer(marker);
}
eventHub.$emit('sendMarkers', getBoundsMarkers);
}
}
}
</script>
<style scoped>
#map {
width: 97%;
height: 100%;
}
</style>
I figured it out myself.
The 'zoomend' and 'dragend' option didn't work for me. I searched a lot for a suitable option and realized that the "moveend" event fires several times because this event is created every time you move the map. Therefore it is necessary to stop this event. I got out of the situation in this way. Immediately after the map was initialized, I wrote:
map.off('moveend');
and for me it worked. Now it works fine. I will be very happy if this is useful to someone.

Vue and data from REST API

While playing around with vue.js I noticed some strange behavior while trying to display on a page data from an API, but here's the strange thing :
using vue 2.0.0, i can see the "Title", but I have an error in dev console [see printscreen]
using the latest vue version, i can't see the "Title" [and I have the same error in the printscreen]
Is it normal, or?
Source code :
template:
'<div>'+
'Form with id = {{id}}'+
'<br/>'+
'has title = {{item.details.Title}}'+
'</div>',
data: function(){
return {
id: '',
item: {}
}
},
created: function() {
this.get()
},
methods: {
get: function() {
var self = this
id = window.location.hash
id = id.replace('#/whatever/','')
axiosInstance.get('/thebackendresource/'+id) // <--- make http calls etc
.then(function (response) {
self.id = id
self.item = response.data
console.log(self.item)
}).catch(function (error) {
console.log(error)
}
);
}
}
You are getting this error, because when you are fetching data from axiosinstance, that time item.details is null, and when it tries to render it throws this error.
Once the api call is completed, it updates the the DOM and in turn re-renders the DOM, so you can see item.details.Title rendered.
You need to add a null check to prevent this error, which can be easily done using v-if, like follwoing:
template:
'<div>'+
'Form with id = {{id}}'+
'<br/>'+
'<span v-if="item.details"> has title = {{item.details.Title}}'+
'</span>' +
'</div>',

How do we do e2e testing with polymer and selenium?

Angular has protractor to listen to lifecycle events in Angular.
eg: ptor.waitForAngular();
Is their a way to get selenium tests to wait for the various life-cycle events in polymer?
Currently we can have simple e2e tests running mocha, to do this is embed the tests directly in the html like how they do it in the source code.
You can just run your tests like they do here.
<script>
document.addEventListener('polymer-ready', function() {
mocha.run();
});
</script>
Eg to check for correct updates in a change watcher you can do this:
Polymer('x-test', {
bar: '',
ready: function() {
this.bar = 'bar';
setTimeout(function() {
this.zonk = 'zonk';
}.bind(this));
},
barChanged: function() {
chai.assert.equal(this.bar, 'bar', 'change in ready calls *Changed');
checkDone();
},
zonkChanged: function() {
chai.assert.equal(this.zonk, 'zonk', 'change calls *Changed without prototype value')
checkDone();
}
});
and for eg if you wanted to check a computer property is correct after the ready event you can do this:
<x-foo foo="mee" bar="too" count=3></x-foo>
<polymer-element name="x-foo" attributes="foo bar count">
<template>{{ fooBar }}:{{ fooBarCounted }}</template>
<script>
Polymer('x-foo', {
computed: {
fooBarCounted: 'repeat(fooBar, count)',
fooBar: "foo + '-' + bar"
},
repeat: function(str, count) {
var retval = '';
for (var i = 0; i < count; i++) {
retval += (i ? ' ' : '') + str + '(' + i + ')';
}
return retval;
},
ready: function() {
chai.assert.equal(this.shadowRoot.innerHTML, 'mee-too:mee-too(0) mee-too(1) mee-too(2)');
done();
}
})
</script>
</polymer-element>
Actually selenium (and other libs based on on it) can wait elements to appear on the page.
E.g. browser.waitForExist('#selector')
See API documentation here http://webdriver.io/api/utility/waitForExist.html
Hope it might be useful for you.

CasperJS can not trigger twitter infinite scroll

I am trying to get some information from twitter using CasperJS. And I'm stuck with infinite scroll. The thing is that even using jquery to scroll the page down nothings seems to work. Neither scrolling, neither triggering the exact event on window (smth like uiNearTheBottom) doesn't seem to help.
Interesting thing - all of these attempts work when injecting JS code via js console in FF & Chrome.
Here's the example code :
casper.thenEvaluate(function(){
$(window).trigger('uiNearTheBottom');
});
or
casper.thenEvaluate(function(){
document.body.scrollTop = document.body.scrollHeight;
});
If casper.scrollToBottom() fails you or casper.scroll_to_bottom(), then the one below will serve you:
this.page.scrollPosition = { top: this.page.scrollPosition["top"] +
document.body.scrollHeight, left: 0 };
A working example:
casper.start(url, function () {
this.wait(10000, function () {
this.page.scrollPosition = { top: this.page.scrollPosition["top"] + document.body.scrollHeight, left: 0 };
if (this.visible("div.load-more")) {
this.echo("I am here");
}
})});
It uses the underlying PhantomJS scroll found here
CasperJs is based on PhantomJS and as per below discussion no window object exist for the headless browser.
You can check the discussion here
On Twitter you can use:
casper.scrollToBottom();
casper.wait(1000, function () {
casper.capture("loadedContent.png");
});
But if you include jQuery... , the above code won't work!
var casper = require('casper').create({
clientScripts: [
'jquery-1.11.0.min.js'
]
});
The script injection blocks Twitter's infinite scroll from loading content. On BoingBoing.net, CasperJS scrollToBottom() works with jQuery without blocking. It really depends on the site.
However, you can inject jQuery after the content has loaded.
casper.scrollToBottom();
casper.wait(1000, function () {
casper.capture("loadedContent.png");
// Inject client-side jQuery library
casper.options.clientScripts.push("jquery.js");
// And use like so...
var height = casper.evaluate(function () {
return $(document).height();
});
});
I have adopted this from a previous answer
var iterations = 5; //amount of pages to go through
var timeToWait = 2000; //time to wait in milliseconds
var last;
var list = [];
for (i = 0; i <= iterations; i++) {
list.push(i);
}
//evaluate this in the browser context and pass the timer back to casperjs
casper.thenEvaluate(function(iters, waitTime) {
window.x = 0;
var intervalID = setInterval(function() {
console.log("Using setInternal " + window.x);
window.scrollTo(0, document.body.scrollHeight);
if (++window.x === iters) {
window.clearInterval(intervalID);
}
}, waitTime);
}, iterations, timeToWait);
casper.each(list, function(self, i) {
self.wait(timeToWait, function() {
last = i;
this.echo('Using this.wait ' + i);
});
});
casper.waitFor(function() {
return (last === list[list.length - 1] && iterations === this.getGlobal('x'));
}, function() {
this.echo('All done.')
});
Essentially what happens is I enter the page context, scroll to the bottom, and then wait 2 seconds for the content to load. Obviously I would have liked to use repeated applications of casper.scrollToBottom() or something more sophisticated, but the loading time wasn't allowing me to make this happen.

FullCalendar and Flot Resize Conflict

I've successfully integrated both a Flot line graph and an instance of FullCalendar into my site. They are both on separate pages (although the pages are loaded into a div via AJAX).
I've added the Flot Resize plugin and that works perfectly, re-sizing the line graph as expected. However, it seems to cause an error when resizing the calendar.
Even if I load the calendar page first, when I resize the window I get this error in the console (also, the calendar does not resize correctly):
TypeError: 'undefined' is not an object (evaluating 'r.w=o!==c?o:q.width()')
I was struggling to work out where the error was coming from, so I removed the link to the Flot Resize JS and tried again. Of course the line graph does not resize, but when resizing the calendar, it works correctly.
The div containers for the two elements have different names and the resize function is called from within the function to draw the line graph (as required).
I have tried moving the link to the Flot Resize plugin into different places (i.e. above/below the fullCalendar JS, into the template which holds the graph), but all to no avail.
Does anyone have any idea where the conflict might be and how I might solve it??
Thanks very much!
EDIT: It seems that the error is also triggered when loading the line graph (flot) page AFTER the fullcalendar page even without resizing the window.... Now I am very confused!
EDIT 2: The code which draws the line graph. The function is called on pageload and recieves the data from JSON pulled off the server. When the graph is loaded, I still get the error about shutdown() being undefined.
function plotLineGraph(theData){
var myData = theData['data'];
var myEvents = theData['events'];
var myDates = theData['dates'];
var events = new Array();
for (var i=0; i<myEvents.length; i++) {
events.push(
{
min: myEvents[i][0],
max: myEvents[i][1],
eventType: "Calendar Entry",
title: myEvents[i][2],
description: myEvents[i][3]
}
);
}
function showTooltip(x, y, contents) {
$('<div id="tooltip">' + contents + '</div>').css( {
position: 'absolute',
display: 'none',
top: y + 5,
left: x + 5,
border: '1px solid #fdd',
padding: '2px',
'background-color': 'black',
opacity: 0.80
}).appendTo("body").fadeIn(200);
}
var previousPoint = null;
$("#placeholder").bind("plothover", function (event, pos, item) {
$("#x").text(pos.x.toFixed(2));
$("#y").text(pos.y.toFixed(2));
if ($("#enableTooltip:checked").length == 0) {
if (item) {
if (previousPoint != item.dataIndex) {
previousPoint = item.dataIndex;
$("#tooltip").remove();
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
if(item.series.label != null){
showTooltip(item.pageX, item.pageY,
item.series.label + " of " + y);
}
}
}
else {
$("#tooltip").remove();
previousPoint = null;
}
}
});
var d1 = [
myData[0], myData[1], myData[2], myData[3], myData[4],
myData[5], myData[6], myData[7], myData[8], myData[9],
myData[10], myData[11], myData[12], myData[13], myData[14],
myData[15], myData[16], myData[17], myData[18], myData[19],
myData[20], myData[21], myData[22], myData[23], myData[24],
myData[25], myData[26], myData[27], myData[28], myData[29]
];
var markings = [
{ color: '#FFBDC1', yaxis: { from: 0, to: 2 } },
{ color: '#F2E2C7', yaxis: { from: 2, to: 3.5 } },
{ color: '#B6F2B7', yaxis: { from: 3.5, to: 5 } }
];
$.plot($("#placeholder"), [
{label: "Average Daily Rating", data: d1, color: "black"}
], {
events: {
data: events,
},
series: {
lines: { show: true },
points: { show: true }
},
legend: { show: true, container: '#legend-holder' },
xaxis: {
ticks:[
myDates[0], myDates[1], myDates[2], myDates[3], myDates[4],
myDates[5], myDates[6], myDates[7], myDates[8], myDates[9],
myDates[10], myDates[11], myDates[12], myDates[13], myDates[14],
myDates[15], myDates[16], myDates[17], myDates[18], myDates[19],
myDates[20], myDates[21], myDates[22], myDates[23], myDates[24],
myDates[25], myDates[26], myDates[27], myDates[28], myDates[29]
],
},
yaxis: {
ticks: 5,
min: 0,
max: 5
},
grid: {
backgroundColor: { colors: ["#fff", "#eee"] },
hoverable: true,
clickable: true,
markings: markings
},
selection: {
color: 'white',
mode: 'x'
},
});
$('#placeholder').resize();
$('#placeholder').shutdown();
}
EDIT 3:
The calendar is called like this:
function showCalendar() {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
$('#fullcalendar').fullCalendar({
header: {
left: 'prev',
center: 'title',
right: 'next'
},
clickable: true,
firstDay: 1,
eventSources: [
{
url: '/populate-calendar/{{theProductUuid}}/',
color: 'black',
data: {
text: 'text'
}
}
],
eventClick: function(calEvent, jsEvent, view) {
var startDate = $.fullCalendar.formatDate(calEvent.start, 'yyyy-MM-dd');
var endDate = $.fullCalendar.formatDate(calEvent.end, 'yyyy-MM-dd');
var eventId = calEvent.uuid;
$('#modal-event-title').text(calEvent.title);
$('#edit-event-name').val(calEvent.title);
$('#edit-start-date').val(startDate);
$('#edit-end-date').val(endDate);
$('#edit-event-text').val(calEvent.text);
$('#edit-event-btn').attr('data-uuid', eventId);
$('#modal-edit-event').on('click', '#delete-btn', function(){
deleteCalendarEvent(eventId);
});
$('#modal-edit-event').modal();
},
});
}
The AJAX to load the page containing the flot chart:
function loadDetailedReports(uuid){
$('#product-content').fadeOut('slow', function(){
$('#product-content').empty();
$('#whole-product-sub-nav .active').removeClass('active');
$('#detailed-reports-content').load('/detailed-reports/' + uuid + '/', function(){
$('#detailed-reports-btn').addClass('active');
$('#detailed-reports-content').fadeIn('slow', function(){
if (authorized){
setLocationHash('loadDetailedReports&' + uuid);
getChartData(uuid);
} else {
setLocationHash('');
}
});
});
});
}
And the AJAX to load the page containing the calendar:
function loadCalendar(uuid){
$('#detailed-reports-content').empty().hide();
$('#product-content').fadeOut('slow', function(){
$('#whole-product-sub-nav .active').removeClass('active');
$('#product-content').load('/calendar/' + uuid + '/', function(){
$('#calendar-btn').addClass('active');
$('#product-content').fadeIn('slow', function(){
if (authorized){
setLocationHash('loadCalendar&' + uuid);
} else {
setLocationHash('');
}
showCalendar();
});
});
});
}
The calls to .resize and .shutdown are there because I was under the impression that they are necessary to achieve the resizing function and in response to your earlier comment regarding shutdown...... They're quite possibly n00b errors........?!?!
It looks like this is triggering on line 198 of jquery-resize:
data.w = w !== undefined ? w : elem.width();
This sounds like a race-condition stemming from the way you load different content into the same div. Flot binds the resize event to the chart div, and only un-binds it if the plot is destroyed cleanly.
EDIT: Looking at your code, my first suggestion would be to get rid of the resize and shutdown calls at the end of plotLineGraph. The resize plugin doesn't require any setup; it hooks into Flot to attach automatically to any new plot. So your call to resize is actually to jQuery's resize event trigger, which may be what's causing the error.
EDIT #2: I'm still not clear on your structure, but to generalize: anywhere that you might be getting rid of #placeholder (via emptying its parent or anything like that) you should first call shutdown on the plot object. If you aren't keeping a reference to it, you can do it like this: $("#placeholder").data("plot").shutdown(); but then have to account for the fact that it's undefined prior to the creation of your first plot.
If that still doesn't work, I'd need to see a live (simplified) example to make any further suggestions.