Reading metadata from the <track> of an HTML5 <video> using Captionator - javascript

I am having trouble getting a working example that reads metadata from a WebVTT file, which was specified by the <track> element of a <video> in an HTML5 page. To be clear, I am not talking about reading the metadata out of the video file itself (as you would with an MPEG Transport Stream, for instance). What I'm talking about is the <track> element that is used for captioning videos. One of the attributes of a <track> is kind, which can be specified as any of the following values:
Subtitles
Descriptions
Captions
Navigation
Chapters
Metadata
I am trying to use the metadata type to access text stored in the corresponding WebVTT file, which I intend to manipulate using JavaScript. I know this is possible, as it is mentioned by Silvia Pfeiffer as well as by the maker of Captionator, which is the JavaScript polyfill that I am using to implement the functionality of interpreting the <track> tags. However, I just can't get it to work.
My code is based on the Captionator documentation's captions example. I added a button to retrieve the metadata and display it when I click the button. Unfortunately it keeps displaying "undefined" instead of the metadata. Any ideas what I might be doing incorrectly? Alternatively, does anyone know where a working example is that I could take a look at? I can't find one anywhere.
If you care to take a look at my code, I've included it below:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Video Closed Captioning Example</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" media="screen" href="js/Captionator-v0.5-12/css/captions.css"/>
</head>
<body>
<h1>HTML5 Video Closed Captioning Example</h1>
<div>
<p id="metadataText">Metadata text should appear here</p>
<input type='button' onclick='changeText()' value='Click here to display the metadata text'/>
</div>
<video controls autobuffer id="videoTest" width="1010" height="464">
<source src="http://localhost:8080/Videos/testVideo.webm" type="video/webm" />
<source src="http://localhost:8080/Videos/testVideo.mp4" type="video/mp4" />
<!-- WebVTT Track Metadata Example -->
<track label="Metadata Track" kind="metadata" src="http://localhost:8080/Videos/Timed_Text_Tracks/testVideo_metadata.vtt" type="text/webvtt" srclang="en" />
</video>
<!-- Include Captionator -->
<script type="text/javascript" src="js/Captionator-v0.5-12/js/captionator.js"></script>
<!-- Example Usage -->
<script type="text/javascript" src="js/Captionator-v0.5-12/js/captionator-example-api.js"></script>
<script type="text/javascript">
window.addEventListener("load",function() {
captionator.captionify(null,null,{
debugMode: !!window.location.search.match(/debug/i),
sizeCuesByTextBoundingBox: !!window.location.search.match(/boundingBox/i),
enableHighResolution: !!window.location.search.match(/highres/i),
});
var videoObject = document.getElementsByTagName("video")[0];
videoObject.volume = 0;
document.body.appendChild(generateMediaControls(videoObject));
},false);
function changeText() {
document.getElementById('metadataText').innerHTML = testVar;
var cueText = document.getElementById("video").tracks[0].activeCues[0].getCueAsSource();
document.getElementById('metadataText').innerHTML = cueText;
}
</script>
</body>
</html>
My WebVTT file looks like this:
WEBVTT
0
00:00.000 --> 00:04.000
Testing 1 2 3 . . .

The way you're accessing the cue is correct - no problems there (although there will be a change in Captionator 0.6 from the .tracks property to the .textTracks property to be more in line with the specification. If you can bear the occasional bug I would recommend using 0.6 for its greater standards compliance - I've written the below code to use .textTracks - substitute for .tracks if you'd like to continue using the stable branch.)
The issue relates to the loading of the text tracks themselves. At the moment, you're not actually telling Captionator to load the track. Because this happens asynchronously, and on request, there is that inevitable delay where their content isn't available, you'll need to write your code in a way which accommodates for loading time and the potential load error.
You're also not waiting for Captionator itself to load - potentially a user could unknowingly click the button before this had occurred - triggering a nasty JavaScript error. This won't be such a problem when testing on your local box, but as soon as you deploy to the internet you'll be seeing all sorts of race conditions and other nasties. Consider disabling the button until both the page and the caption data have loaded.
I've tried to make the Captionator API as close as possible to the actual JS API which will be landing in browsers very soon - so in future this will be the same way you'll interact with the native browser functionality. As soon as the functionality is available natively, Captionator will bow out of the way, and your code should (assuming they don't change the API again!) just work with the native API.
First of all, you need to actually request that Captionator load the content. This is done my setting the 'display mode' of the track to SHOWING, or 2.
var video = document.getElementByID("myVideo");
video.textTracks[0].mode = 2; // SHOWING
Alternately, you can assign the status of a track to HIDDEN (1) - which still triggers a load, and cueChange events will still fire - but won't paint cues to screen. In Captionator, I don't paint metadata tracks to screen at all, but the (buggy) WebKit API in development will.
video.textTracks[0].mode = 1; // HIDDEN
Then you need to listen for when the cues are loaded and available:
video.textTracks[0].onload = function() { /* Your Code Here... */ }
Or when something goes wrong:
video.textTracks[0].onerror = function() { /* Whoah, something went wrong... */ }
Once the content is loaded, you can access the TextTrack.cues array (well, technically a TextTrackCueList.) Before the load has occurred, the TextTrack.cues property will be null.
var myCueText = video.textTracks[0].cues[0].text;
Be aware that Captionator parses the cue text of every cue, except when the track kind is metadata - so ensure you assign the correct track kind. You might end up with data or tags Captionator thinks are 'invalid' being thrown out. You can turn this check off for regular cues as well, with by setting the processCueHTML option to false.
With that in mind, here's how I'd rewrite your code:
<div>
<p id="metadataText">Metadata text should appear here</p>
<input type='button' onclick='changeText()' value='Click here to display the metadata text' id="changetext" disabled />
</div>
<video controls autobuffer id="videoTest" width="512" height="288">
<!-- Your video sources etc... -->
<!-- The metadata track -->
<track label="Metadata Track" kind="metadata" src="metadata.vtt" type="text/webvtt" srclang="en" />
</video>
<!-- Include Captionator -->
<script type="text/javascript" src="captionator.js"></script>
<script type="text/javascript">
document.addEventListener("readystatechange",function(event) {
if (document.readyState === "complete") {
captionator.captionify();
document.querySelectorAll("#changetext")[0].removeAttribute("disabled");
}
},false);
function changeText() {
// Get the metadataText paragraph
var textOutput = document.querySelectorAll("#metadataText")[0];
// Get the metadata text track
var metadataTrack = document.querySelectorAll("video")[0].textTracks[0];
if (metadataTrack.readyState === captionator.TextTrack.LOADED) {
// The cue is already ready to be displayed!
textOutput.innerHTML = metadataTrack.cues[0].text;
} else {
// We check to see whether we haven't already assigned the mode.
if (metadataTrack.mode !== captionator.TextTrack.SHOWING) {
textOutput.innerHTML = "Caption loading...";
// The file isn't loaded yet. Load it in!
metadataTrack.mode = captionator.TextTrack.SHOWING; // You can use captionator.TextTrack.HIDDEN too.
metadataTrack.onload = function() {
textOutput.innerHTML = metadataTrack.cues[0].text;
}
metadataTrack.onerror = function() {
textOutput.innerHTML = "Error loading caption!";
}
}
}
}
</script>
Here, we're disabling the button, preventing users on slow connections (or just somebody with very quick reflexes!) from hitting it before either Captionator or the metadata track are ready, and listening to a load event - at which point we re-enable the button, and can retrieve the cue text as normal.

You may need to load your metadata VTT file via Ajax and parse and display it yourself.
I looked at the example from the HTML5 Doctors' article on video subtitling. They're using Playr, so I checked out its source code, and they're definitely requesting the VTT file asynchronously and parsing the content once it's loaded.
I was able to load the contents of the VTT file and dump it into the specified element with the following code:
function changeText() {
var track = document.getElementById("videoTest").querySelector("track");
var req_track = new XMLHttpRequest();
req_track.open('GET', track.getAttribute("src"));
req_track.onreadystatechange = function(){
if(req_track.readyState == 4 && (req_track.status == 200 || req_track.status == 0)){
if(req_track.responseText != ''){
document.getElementById("metadataText").innerHTML = req_track.responseText;
}
}
}
req_track.send(null);
}
I'm not familiar with Captionator, but it looks like it has some capabilities for parsing VTT files into some sort of data structure, even if it doesn't necessarily support the metadata track type. Maybe you can use a combination of this code and Captionator's existing VTT parser?

Related

Google AMP and custom Javascript

According to the docs, all I need to do is to wrap the block I'd like to "talk to" via Javascript with this:
<amp-script layout="container" src="language-toggle.js">
// Some basic HTML
</amp-script>
The Javascript file is there, I tested with a simple console.log. Yet the amp-script tag has opacity: 0.7 (AMP default style). Apparently, it needs the class i-amphtml-hydrated to be fully visible. I've been trying to wrap my head around this for a few hours now, even Google could not help me with this.
There are a bunch of ServiceWorker errors in the console, which are also all generated by AMP. I have no idea why they appear or how to get rid of them. This whole AMP thing is a mess for me.
These are the AMP scripts I currently added:
<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-script" src="https://cdn.ampproject.org/v0/amp-script-0.1.js"></script>
<script async custom-element="amp-carousel" src="https://cdn.ampproject.org/v0/amp-carousel-0.1.js"></script>
<script async custom-element="amp-youtube" src="https://cdn.ampproject.org/v0/amp-youtube-0.1.js"></script>
Carousel and YouTube are working fine.
Could anybody please shed some light onto this?
I highly recommend to enable AMP development mode by adding #development=1 to the URL.
Relative URL's are not allowed in the src attribute of the amp-script tag (the development parameter would have told you that).
You can have something like this though:
<amp-script width="1" height="1" script="demo"></amp-script>
<script type="text/plain" target="amp-script" id="demo">
console.log('Foobar');
</script>
But you will need a matching hash in a meta tag in your head:
<head>
...
<meta
name="amp-script-src"
content="sha384-hash"
/>
</head>
Again, the development parameter will tell you the hash you should use, although you could also disable hash checks during development.
All of the above will still not hydrate your amp-script element. In order for your element to be hydrated, the script has to actually to something to the DOM, like for example adding a div on a button click:
<amp-script layout="container" script="demo">
<button id="hello">Add headline</button>
</amp-script>
<script type="text/plain" target="amp-script" id="demo">
console.log('Foobar');
const button = document.getElementById('hello');
button.addEventListener('click', () => {
const h1 = document.createElement('h1');
h1.textContent = 'Hello World!';
document.body.appendChild(h1);
});
</script>
Be aware that you are quite limited with what you are allowed to do. For example, the above snippet will not work without the event listener, so you can not simply add an element without user interaction.
The messages regarding the references can safely be ignored - the AMP examples do exaclty the same, the AMP still passes the validation.

How can I make a 'notification' sound play when a webpage is opened on an Android phone?

I've been searching today and have found some answers, such as this:
<head>
<title>Audio test</title>
<script type="text/javascript">
// #param filename The name of the file WITHOUT ending
function playSound(filename){
document.getElementById("sound").innerHTML='<audio autoplay="autoplay"><source src="' + filename + '.mp3" type="audio/mpeg" /><source src="' + filename + '.ogg" type="audio/ogg" /><embed hidden="true" autostart="true" loop="false" src="' + filename +'.mp3" /></audio>';
}
</script>
</head>
<body>
<button onclick="playSound('bing');">Play</button>
<div id="sound"></div>
</body>
For the answer above, I believe I just need to enter the correct file name (ex. mydomain.com/correctfilename.mp3) where 'filename' is in that script.
But I'm looking for something a little different and a little faster load-time wise. I'm wondering if I can have a 'default' Android notification sound played when the site is opened. I've found something similar to what I need right here:
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
} catch (Exception e) {
e.printStackTrace();
}
But when I put that in the HTML file, it doesn't work, and I think it's because it's not javascript.
I know there are ways to access whatever your phone has that makes it vibrate, so I'm thinking there could be a way to access the pre-loaded sounds it has. Can I do this with a script?
I think you are asking if you can do something from the web page, not from a specific app on the device - for that way use WebView.
For any web page to access device services via the browser, there is the W3C Device API Working Group, which has produced a Vibration API, and even works already on Android Chrome Beta 39 and others like so:
navigator.vibrate(2000);
I'm not aware, however, of some way to play a default sound/vibration that follows the device environment like say, Windows API handles it.

set the src attribute of an iframe, then load into page, using javascript

I'm currently working on a simple google chrome extension to play youtube videos in a separate tab based on an omnibox query. For example, if a user searches for "tupac", my extension redirects to chrome-extension://{chrome-extension-id}/just-play-music.html?tupac.
Now I want to embed a youtube iframe player on this just-play-music.html page, set to play a list of videos returned by a search on the string "tupac". Following the Google API docs, this is achieved easily enough by providing the following src url for my iframe: http://www.youtube.com/embed?listType=search&list=QUERY where QUERY in this case would be replaced by "tupac".
However, here is where I've reached a sticking point. I can't figure out how to configure my .html and .js files so that this happens dynamically when the page loads, based on the url of the page. Below is the code for just-play-music.html and the .js file it is linked to.
Again, the goal here is to generate a youtube iframe with a src attribute created by grabbing location.search (I realize this is an inelegant way of doing this) when the page loads, and then insert this iframe into the content section. Please let me know if you see something very wrong, this is admittedly my first time working with javascript.
html
<html>
<head>
<link rel="stylesheet" href="main.css" type="text/css">
<script src="main.js"></script>
</head>
<body>
<section class="header">
<h1>just play music:</h1>
</section>
<section id="movie" class="content">
</section>
</body>
</html>
javascript
function iframeDidLoad() {
alert("Done");
}
function loadIframe() {
var query = location.search;
var target = "http://www.youtube.com/embed?listType=search&autoplay=1&list=" + encodeURIComponent(query);
var frame = '<iframe id="ytplayer" type="text/html" width="640" height="390" src="'+target+'" frameborder="0" />';
document.getElementById('movie').innerHTML = frame;
iframeDidLoad();
}
You should do a console.log on the location.search. It gives you a string you need to parse, e.g., "?key=value&key2=value2&etc=another+value". I think you might need to address that.
So if where it says list= needs to be just tupac, you're going to need to convert your search string from a string that looks like this: ?query=tupac to a string that looks like this tupac by doing something like:
var query = location.search.split('=')[1];
What this does is it splits the string up into two pieces, using the = sign to divide them, then using [1] to select the second piece (array indices start from 0). Then you'll have just the tupac portion of the string (or whatever the query string is for [e.g., U2]).
If your query string has multiple parameters, you needs to parse each of those first, e.g., if your query string looks like this:
?somekey=somevalue&query=tupac&someotherkey=some+other+value
you're going to need to do something like this:
var query;
location.search.split('&').forEach(function(piece) {
if (piece.indexOf('query=') !== -1)
query = piece.split('=')[1];
});

HTML5 Video link not working in Chrome

I have the following code for viewing my webcam directly via a publicly accessible link.
<!DOCTYPE html>
<html>
<head>
<title>webRTC Test</title>
</head>
<script type = "text/javascript">
function init()
{
if(navigator.webkitGetUserMedia)
{
navigator.webkitGetUserMedia({video:true}, onSuccess, onFail);
}
else
{
alert('webRTC not available');
}
}
function onSuccess(stream)
{
document.getElementById('camFeed').src = webkitURL.createObjectURL(stream);
var src = document.getElementById('camFeed').getAttribute('src');
document.getElementById('streamLink').href = src;
}
function onFail()
{
alert('could not connect stream');
}
</script>
<body onload = "init();" style="background-color:#ababab;">
<div style="width:352px; height:625px; margin:0 auto; background-color:#fff;">
<div>
<video id ="camFeed" width="320" height="240" autoplay>
</video>
</div>
<div>
<canvas id="photo" width="320" height="240">
</canvas>
</div>
<div style="margin: 0 auto; width:82px;">
<a id="streamLink">Visit Stream</a>
</div>
</div>
</div>
</body>
</html>
The link generated in the anchor tag is something like:
blob:http%3A//sitename.com/7989e43a-334r-4319-b9c5-9dfu00b00cd0
And upon visiting chrome tells me "Oops! This link appears to be broken."
Help appreciated!
The File API spec defines URL.createObjectURL. There are a couple of sections that make what you're trying to do impossible in a browser that follows the spec.
Section 11.5 says:
The origin of a Blob URI must be the origin of the script that called URL.createObjectURL. Blob URIs must only be valid within this origin.
In other words, the URIs returned by createObjectURL can only be used within the context of the website that created them (see RFC6454: The Web Origin Concept for a more precise definition of what the HTML specs mean by “origin”). You can't visit a URL returned by createObjectURL directly.
Section 11.6 says:
This specification adds an additional unloading document cleanup step: user agents must revoke any Blob URIs created with URL.createObjectURL from within that document.
This means that even if you could visit the URL directly, as soon you you leave the page that called createObjectURL the URL that was created ceases to exist.
You must ensure that you’re using/testing your code at HTTP or HTTPs protocols --- because URL.createObjectURL has some issues at file:// protocol --- and it can’t be able to generate right BLOB for your video while using file:// ---- !!!
Your code won't work on localhost or your machine alone.
All you need is, upload this HTML document on the Net(just in case you are wondering on how to get Hosting for yourself, then try checkout Dropbox, you can upload your HTML page publicly and get access via Public Link for free or try some other product or simply get hosting for yourself). As you can see that this example http://www.html5rocks.com/en/tutorials/getusermedia/intro/ works perfectly in chrome, though the code that it is utilising is the same as yours. I hope this solution is of some help to your and others searching for an answer to this bug.
Also, you can then use an iframe to get access to the video element to perform operations on it.

Setting data attribute for object element in html

i have an object element in my html body to show an Active reports which exports to a .pdf file. I need to use javascript to automatically print the pdf out to the client's default printer and then save the pdf to the server:
<script language="javascript" type="text/javascript">
// <!CDATA[
function PrintPDF() {
pdf.click();
pdf.setActive();
pdf.focus();
pdf.PrintAll();
}
// ]]>
....
<body onload="return PrintPDF();">
<form id="form1" runat="server">
<object id="pdfDoc" type="application/pdf" width="100%" height="100%" data="test.aspx?PrintReport=yes&SavePDF=yes"/>
</form>
</body>
With the data hard-code in the object tag, everything run without a problem.
The problem now is that I need to pass querystring to this page dynamically. I tried to set the attribute data in the javsacript to pass the querystring. The querystring value passed successfully, but the data attribute does not seem to be set. I get a blank page.
pdf.setAttribute("data","test.aspx?PrintReport=yes&SavePDF=yes&AccNum="+AccNum);
Does anyone have a clue how I can set the data attribute dynamically to pass in querystring?
Thanks,
var pdfObj = document.getElementById('pdfDoc');
pdfObj.data="test.aspx?PrintReport=yes&SavePDF=yes&AccNum="+AccNum;
As far as the data attribute you're doing everything fine. Here are some examples:
http://jsfiddle.net/3SxRu/
I think your problem might be more to do with the order of execution. What does your actual code look like? Are you writing over the body onLoad function or something?
Also, I assume using the data attribute is a requirement. HTML5 defines data-*. This attribute isn't really valid. Again, maybe your system requires it.
I suspect that things are happening out of order. Try waiting until the onload event of the window before adding the embed.
Also, I suggest using a script like PDFObject to handle the embedding since it is a reliable way to embed PDF across all the various browsers out there. For example you might have something like the following:
<html>
<head>
<title>PDFObject example</title>
<script type="text/javascript" src="pdfobject.js"></script>
<script type="text/javascript">
window.onload = function (){
// First build the link to the PDF raw data ("bits")
// getQueryStrings assumes something like http://stackoverflow.com/questions/2907482/how-to-get-the-query-string-by-javascript
var queryStrings = getQueryStrings();
var reportNameParamValue = queryStrings["reportName"];
var pdfBitsUrl = "getReportPdfBits.aspx?reportName=" + reportNameParamValue;
// just in case PDF cannot be embedded, we'll fix the fallback link below:
var pdfFallbackLink = document.getElementById("pdfFallbackAnchor");
pdfFallbackLink.href = pdfFallbackLink;
// now perform the actual embed using PDFObject script from http://pdfobject.com
var success = new PDFObject( {
url: pdfBitsUrl;
}).embed();
};
</script>
</head>
<body>
<p>It appears you don't have Adobe Reader or PDF support in this web
browser. <a id="pdfFallbackAnchor" href="sample.pdf">Click here to download the PDF</a></p>
</body>

Categories