Throttling HTML5 Video currentTime - javascript

I have an app that tracks video views and integrates it with other marketing activities. In doing so, I needed to keep track of how long a person watches a html5 video and post it back to my app (via an API). I'm using videojs player, but really this is just a wrapper around the HTML5's api for this attribute. This is in an app with various videos can be loaded based on what page they are watching, so I needed a solution that tracked regardless of video length.
The problem I had, as a video plays the API reports back every ~300MS and I didn't want to hit my API that often. So I needed a solution to keep track of last time I posted. After digging around, I couldn't find an answer, so in case someone else with a similar need, my solution to this problem is below.

We've decided that I wanted to post my video viewing results every 5 seconds, but since we have no guarantee that the currentTime will report back at exactly 5 seconds, so we just need to round to closest whole integer value.
On my video wrapper div, I've added a data attribute called data-last-time-push. I post the rounded time every time I push and check to see if we have exceed the interval before we post again.
HTML
<div id="video-wrapper" data-time-last-push="0">
Javascript
Bind the videojs container to the timeupdate property.
var vid = videojs("video-container", {}, function() {
this.on('timeupdate', videoTracker);
});
function for posting ajax...
var videoTracker = function() {
var player = this;
var last_push, wrapper, current;
wrapper = $('#video-wrapper');
last_push = wrapper.attr("data-time-last-push");
current = Math.round(player.currentTime());
//you could make the 5 here to be a variable or your own interval...
if (current%5 === 0) {
if (current > last_push) {
//do your AJAX post here...
wrapper.attr("data-time-last-push", current);
console.log('currentTime = ' + player.currentTime());
console.log(' duration: ' + player.duration());
}
}
};
Note, I tried to do a jsfiddle to show it working, but ended up running into HTTPS videos because the sample videos don't work through secure connections.

Related

Youtube iframe API with javascript tabs

I'm building an online 'TV' which will use YouTube live-streams for multiple channels.
The channels are contained within tabs. The videos need to be stopped when changing tabs otherwise you can hear the audio in the background.
Here's a link to the JSFiddle: https://jsfiddle.net/matlow/08k4csuh/
I've managed to turn the 'Channel 1' off when changing to another channel with:
var iframe = document.getElementsByClassName("tvscreen")[0].contentWindow;
and
iframe.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
In the tab javascript for loop which also handles the tabcontent[i].style.display = "none";
I think I need to use the for loop to call each instance of the iframe... but I'm quite new to javascript so I'm not quite sure how to achieve this.
It will also help to use iframe.postMessage('{"event":"command","func":"playVideo","args":""}', '*'); so the video plays automatically again when clicking on the relevant tab... but again I'm not quite sure how to implement this.
I've been working on this for a few days so if anyone had any tips or pointers I would really appreciate it!
Thanks for reading! :)
You are not using YouTube's API properly. See https://developers.google.com/youtube/iframe_api_reference
In your fiddle, programmatic play is not possible, because you can't know when the YouTube player is ready, as you are not the one initialising it. Your attempts to play the video might take place too early.
Programmatic pause (you managed to pause the first video) is possible thanks to enablejsapi=1 in the iframe src and the fact that the player is ready at that point.
Here's a fork of your fiddle - https://jsfiddle.net/raven0us/ancr2fgz
I added a couple of comments. Check those out.
// load YouTube iframe API as soon as possible, taken from their docs
var tag = document.createElement('script');
tag.id = 'iframe-demo';
tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// initialised players are kept here so we don't have to destroy and reinit
var ytPlayers = {};
function mountChannel(channel) {
var player;
var iframeContainer = document.querySelectorAll('#' + channel + ' iframe');
// if the channel & iframe we want to "mount" exist, check for playing iframes before doing anything else
if (iframeContainer.length > 0) {
// Object.keys() is ECMA 5+, sorry about this, but no easy to check if an object is empty
// alternatively, you could have an array, but in that case, you won't be able to fetch a specific player as fast
// if you don't need that functionality, array is as good cause you will just loop through active players and destroy them
var activePlayersKeys = Object.keys(ytPlayers);
if (activePlayersKeys.length > 0) { // if players exist in the pool, destroy them
for (var i = 0; i < activePlayersKeys.length; i++) {
var activeChannel = activePlayersKeys[i];
var activePlayer = ytPlayers[activeChannel];
activePlayer.getIframe().classList.remove('playing'); // mark pause accordingly, by removing class, not necessary
activePlayer.pauseVideo();
}
}
// check if player already initialised and if player exists, check if it has resumeVideo as a function
if (ytPlayers.hasOwnProperty(channel)) {
ytPlayers[channel].playVideo();
} else {
var iframe = iframeContainer[0];
player = new YT.Player(iframe, {
events: {
'onReady': function (event) {
// event.target is the YT player
// get the actual DOM node iframe nad mark it as playing via a class, styling purposes, not necessary
event.target.getIframe().classList.add('playing');
// play the video
event.target.playVideo();
// video may not autoplay all the time in Chrome, despite its state being cued and this event getting triggered, this happens due to a lot of factors
},
// you should also implement `onStateChange` in order to track video state (as a result of user actions directly via YouTube controls) - https://developers.google.com/youtube/iframe_api_reference#Events
}
});
// append to the list
ytPlayers[channel] = player;
}
}
}
// Get the element with id="defaultOpen" and click on it
function onYouTubeIframeAPIReady() {
// YouTube API will call this when it's ready, only then attempt to "mount" the initial channel
document.getElementById("defaultOpen").click();
}
This is the first time I worked with YouTube's iframe API, but it seems reasonable.

How to listen volume changes of youtube iframe?

Here I found an example how I can listen Play\Pause button of youtube iframe.
player.addEventListener('onStateChange', function(e) {
console.log('State is:', e.data);
});
Now I need to listen the volume changes.
In the youtube documentation and here I found a method player.getVolume(), but I have no idea how this method can be implemented if I want to be informed about volume changes from iframe side, instead of ask iframe from my side.
On YouTube Player Demo page such functionality exists (when I change the volume of a player, I see appropriate changes in the row Volume, (0-100) [current level: **]), but neither in the doc nor in internet I can not find how to implement it.
I also tried to use the above mentioned code with onApiChange event (it is not clear for me what this event actually does), like:
player.addEventListener('onApiChange', function(e) {
console.log('onApiChange is:', e.data);
});
but console shows nothing new.
player.getOptions(); shows Promise {<resolved>: Array(0)}.
Could anyone show an example?
See this question.
You can listen to postMessage events emitted by the IFrame and react only to the volume change ones:
// Instantiate the Player.
function onYouTubeIframeAPIReady() {
var player = new YT.Player("player", {
height: "390",
width: "640",
videoId: "dQw4w9WgXcQ"
});
// This is the source "window" that will emit the events.
var iframeWindow = player.getIframe().contentWindow;
// Listen to events triggered by postMessage.
window.addEventListener("message", function(event) {
// Check that the event was sent from the YouTube IFrame.
if (event.source === iframeWindow) {
var data = JSON.parse(event.data);
// The "infoDelivery" event is used by YT to transmit any
// kind of information change in the player,
// such as the current time or a volume change.
if (
data.event === "infoDelivery" &&
data.info &&
data.info.volume
) {
console.log(data.info.volume); // there's also data.info.muted (a boolean)
}
}
});
}
See it live.
Note that this relies on a private API that may change at anytime without previous notice.
I inspected the code of YouTube Player Demo page and found that the html line which shows the current YouTube volume (<span id="volume">**</span>) constantly blinking (~ 2 times per 1 sec), so I can assume this demo page uses something like this:
// YouTube returns Promise, but we need actual data
self = this
setInterval(function () { self.player.getVolume().then(data => { self.volumeLv = data }) }, 250)
Possibly not the best method, but it seems there is no other option (I also tried to listen changes in the appropriate style of the volume bar, but no luck due to the cross-origin problem).
So, this let us 'listen' volume changes of youtube.
Just in case, if someone wants to set youtube volume, you need to use [this.]player.setVolume(volume_from_0_to_100)

Is this the best way to buffer audio in javascript?

I have streaming audio in wave format being played out through html5 capabilities of firefox. Because some of our users complained about choppy audio we decided to come up with a strategy for measuring if the rate of the audio was good enough to sustain playback. We first tried the canplaythrough event, but apparently the browser was too optimistic about it and it did not quite work. Here is some code that I am proposing using jquery. I am a js/jquery beginner so i wanted to see if anyone had any better ideas.
$(document).ready(function() {
var previous_buffer = 0;
var start_time = (new Date()).getTime();
// function to check how fast buffer is
function checkBufferSpeed() {
// if we havent started buffering yet, return
if (this.buffered.length < 1)
{
return;
}
// get the current buffer status
var current_buffer = this.buffered.end(0);
console.log("current_buffer:" + current_buffer);
// if we get the same current buffer twice, browser is done buffering
if (current_buffer > 0 && current_buffer == previous_buffer)
{
this.play();
$("#audio").off("progress");
return;
}
// get the time spent
var time_spent = ((new Date()).getTime() - start_time)/1000;
console.log("time_spent:" + time_spent);
// if we buffered faster than time spent, play
if (current_buffer > time_spent)
{
this.play();
$("#audio").off("progress");
return;
}
previous_buffer = current_buffer
}
$("#audio").on("progress", checkBufferSpeed);
});
also, the reason i am not checking the audio elements duration variable because firefox seems to always report that as infinite, unless the stream is already cached.
also, it seems like firefox always fires 2 progress events even if the stream is fully available in cache which is why i have the check to see if i have the same buffer twice and if so, to just play the sound.
is there a chance that the browser will never fire progress events?
any enhancements I could make to this code to make it better?

Play after seeking currentTime

I'm trying to control html5 video with javascript. What I want is that when the user clicks on a button, the video will jump to another frame and keep playing from there. With my current code the playback always stops after the seek.
function jumpToTime(){
var video = $("video")[0];
video.currentTime = 160;
document.getElementbyId("videoclip").play(); //not working
};
//the following code is probably not really related to the question:
var endtimeoffset = 2000;
video.addEventListener("timeupdate", function() {
if (video.currentTime >= endtimeoffset) {
video.pause();
}
}, false);
I ran into a similar problem, and found a solution by pausing the video, then setting the currentTime, then playing the video. To update your code:
function jumpToTime(){
var video = $("video")[0];
video.pause();
video.currentTime = 160;
video.play();
};
Some things I would try:
in the jumpToTime() function, you have two different references to supposedly the same video (one obtained through jQuery and the other by getElementById()). Are you sure these reference the same video? To be safe, I would just call play() on the 'video' reference that you set the currentTime on.
This is probably a copy and paste issue since the console would complain if this was in the actual code, but you did mispell getElementById() (Need to capitalize the B).
For debugging purposes, I would comment out the 'timeupdate' event code, to make sure this isn't the issue and that this code isn't pausing the video after you update the timehead or call play. It probably isn't, since you are setting the current time to be much less than the offset you are comparing it with. It would, however, be an easy test to eliminate this as a possible cause of the issue.
function jumpToTime(){
document.getElementById("videoclip").currentTime = 160;
document.getElementById("videoclip").play(); //not working
};
getElementbyId --> getElementById -- b --> B
get direct object by id...

mediaElementjs: how to get instance of the player

I'm stuck with a little problem with MediaElement.js player.
To get the instance of the player, I do this (works with html5 compatible browser):
// Get player
this.playerId = $('div#shotlist-player video').attr('id');
this.player = window[this.playerId];
But it's not working as soon as it fallback in flash. In fact, it's not working because I'm not calling an instance of MediaElement itself. But I don't see how I can call it.
The player is created with
$('video').mediaelementplayer({....});
How can I get the mediaelement object?
------------EDIT----------------
Ok I finally found how to make it works:
// Get player
mePlayer = $('div#shotlist-player video.video-js')[0];
this.player = new MediaElementPlayer(mePlayer);
Now I can user mediaElement instance correctly.
This post is a lot of speculation, but may be correct. Docs are lacking (;
The answer by sidonaldson is perfectly acceptable if you wish to create a new MediaElement instance and get a handle on it. If there's one already present, it seems to try to reinitialize another instance on that element and freaks out.
I am pretty sure mediaelement.js augments the builtin HTML5 controls by providing a JavaScript API to manipulate Flash/Silverlight players via those elements. I may be wrong, but other advice I've seen on this issue in multiple places is to do something like:
$playButton.click(function() {
$('video, audio').each(function() {
$(this)[0].player.play();
});
});
To create a play button as an external DOM element which will fire off all players on the page. This indicates to me that the implementation is something like I've described.
Try:
var player = $('video').mediaelementplayer({
success: function (me) {
me.play();
}
});
// then you can use player.id to return the id
// or player.play();

Categories