While experimenting a little with the capabilities of Windows 8 Metro js apps (the ones created using HTML/Javascript/CSS, meant to be published on the Windows Store of the upcoming Windows 8), I went and created a very simple test application that creates dynamically an iframe-based YouTube player, tied to a specific video. So far, so good.
Later, I wanted to be able to get feedback from the video being played at that iframe. Google offers extensive help on achieving this, and explains that you need to, dynamically, create a tag pointing to the YouTube iframe API, and then, once it finishes loading, it will automatically call a "onYouTubeIframeAPIReady()" function in your code, so it knows the API is ready to operate with your recently created player, by calling functions that manipulate the player, and others than return statistics, and even attach event handlers that monitor the state of the player. Easy enough, right?
Unfortunately, that's not the case with Windows 8 Metro js apps.
It turns out that, because of apps running in what's called the "local context", apart and distinct from the "web context", you cannot actually insert < script > tags pointing to remote resources, on the Web, for these apps. This meaning, I would never be able to get access to the YouTube iframe API to handle my player.
Now, examining the code being referenced in the < script > tag, I realized that, as of today, there are 2 related .js files, one actually attempting to launch the other, with that one being (apparently) self-contained. Upon seeing this, I grabbed a copy of both .js files, stored them into my project, did all the changes so the project referenced them locally, instead of remotely, and voila! Now I have fully-functional feedback from my iframe YouTube player!
However, in spite of having succeeded, I can't quite help but think I'm cheating on here. I'm saving a copy of two .js files that I shouldn't be in control of. All this feels like a, um, "dirty hack" just to make things work in my app.
So, knowing this, I ask you: Is there any other way, whatsoever, that I could query AND control an iframe-based YouTube player embedded into my Windows 8 Metro js app, knowing that due to "local context" restrictions, I simply can't reference the iframe API from Google?
Also, it could very well be that the iframe-based player is not the real solution for what I'm intending to achieve (which is, being able to get timely feedback from the player). If so, what else do you recommend? I'm open to most suggestions in here.
If you're interested, these are the "default.html" and "default.js" of my test app, so you can see what I'm talking about:
default.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>PruebaYouTube</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
<!-- PruebaYouTube references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body>
<p id="contentGoesHere">Content goes here</p>
<div id="playerPlaceholder"></div>
<button id="createPlayer" style="display: none">Create Player</button>
</body>
</html>
default.js: (yt_iframe_api.js and yt_widgetapi.js are my copies of the YouTube API files)
// For an introduction to the Blank template, see the following documentation:
// http://go.microsoft.com/fwlink/?LinkId=232509
var currentPlayer,
embeddedText;
function initApp() {
var ytScript = document.createElement('script');
ytScript.src = "/js/yt_iframe_api.js";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(ytScript, firstScriptTag);
}
function onYouTubeIframeAPIReady() {
var createPlayerButton = document.getElementById("createPlayer");
createPlayerButton.style.display = "";
}
function onPlayerReady(event) {
while (embeddedText.firstChild) {
embeddedText.removeChild(embeddedText.firstChild);
}
embeddedText.appendChild(document.createTextNode("Listo!"));
}
function onPlayerStateChange(event) {
while (embeddedText.firstChild) {
embeddedText.removeChild(embeddedText.firstChild);
}
embeddedText.appendChild(document.createTextNode("Estado: " + event.data));
}
function createYouTubePlayer(mouseEvent) {
var playerPlaceholder = document.getElementById("playerPlaceholder");
while (playerPlaceholder.firstChild) {
playerPlaceholder.removeChild(playerPlaceholder.firstChild);
}
var playerFrame = document.createElement("iframe");
playerFrame.id = "playerFrame";
playerFrame.setAttribute("type", "text/html");
playerFrame.style.position = "absolute";
playerFrame.style.top = "100px";
playerFrame.style.left = "100px";
playerFrame.width = "640px";
playerFrame.height = "390px";
playerFrame.src = "http://www.youtube.com/embed/u1zgFlCw8Aw?controls=0&autoplay=1";
playerFrame.frameBorder = "0";
playerPlaceholder.appendChild(playerFrame);
embeddedText = document.createElement("p");
embeddedText.style.fontSize = "24px";
embeddedText.style.position = "absolute";
embeddedText.style.top = "150px";
embeddedText.style.left = "150px";
playerPlaceholder.appendChild(embeddedText);
currentPlayer = new YT.Player('playerFrame', {
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function attachAllEvents() {
var createPlayerButton = document.getElementById("createPlayer");
createPlayerButton.addEventListener("click", createYouTubePlayer, false);
}
(function () {
"use strict";
WinJS.Binding.optimizeBindingReferences = true;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
initApp();
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
}
args.setPromise(WinJS.UI.processAll().done(attachAllEvents));
}
};
app.oncheckpoint = function (args) {
// TODO: This application is about to be suspended. Save any state
// that needs to persist across suspensions here. You might use the
// WinJS.Application.sessionState object, which is automatically
// saved and restored across suspension. If you need to complete an
// asynchronous operation before your application is suspended, call
// args.setPromise().
};
app.start();
})();
I made it in this way, I hope it can help you. I used jQuery for ajax calls and Player Framework to show the video:
It is not perfect but you got the idea.
Here is where i took the code: https://mytoolkit.svn.codeplex.com/svn/Shared/Multimedia/YouTube.cs
PS: Sorry for my bad english I'm from Perú ;)