<video />
player to be an equivalent experience to our Flash version.Based on market share.
Internet Explorer |
Chrome | Firefox | Safari | Mobile Safari |
Android Browser |
---|---|---|---|---|---|
7, 8 & 9 | Latest | Latest | Latest | 4.3, 5.1 & 6.0 | 3.2, 4.0.4 & 4.1 |
nfl-video
is a YUI module.
Video.Player
, inherits from Y.Widget
.When initialized, Video.Player
detects the browsers capabilities, then calls Y.use
for one of several plugins that all inherit from Plugin.Base
:
<video />
elementVideo.Player
proxies its methods to the plugin.Our seed file sets up YUI to look for our our code.
var YUI_config = {
// YUI config
groups: {
nflui: {
base: "http://...",
comboBase: "http://...",
combine: true,
filter: {
searchExp: '([^\\/]+)\\/\\1-min\\.(js|css)',
replaceStr: "$1/$1.$2"
},
modules: {
// module dependency tree
}
}
}
};
Create an empty element on the page to render the player into.
<div id="player"></div>
Load the seed file.
<script src="http://.../min/?12345&g=nflui"></script>
Use the module like any other in YUI.
YUI().use("nfl-video", function (Y) {
// Y.NFL.Video.Player is ready
});
var player = new Y.NFL.Video.Player({
configURL: "http://../config.json",
contentId: video_id,
height: 360,
width: 640,
render: "#player"
});
width
, height
& render
are inherited from Widget.configURL
loads a site-wide configuration.contentId
loads metadata for a specific video.Complete code example.
<div id="player"></div>
<script src="http://.../min/?12345&g=nflui"></script>
<script>
YUI().use("nfl-video", function (Y) {
var player = new Y.NFL.Video.Player({
configURL: "http://../config.json",
contentId: video_id,
height: 360,
width: 640,
render: "#player"
});
});
</script>
The player supports a few public methods:
player.loadContentId(video_id);
player.pauseVideo();
…and attributes:
player.set("continuous", true);
player.set("nextVideo", {
id: next_video_id,
thumbnail: "http://…",
headline: "…"
});
Video.PlayerEvent:
|
|
/**
Loads a JSON file from the supplied URL
@method loadJSON
@param {String} url URL of the JSON file
@param {Boolean} autoplay Whether or not to begin playback immediately after loading.
@chainable
**/
Runing:
$ yuidoc -o doc/js js
Generates…
/**
* Creates an instance of an VideoPlayer.
*
* @constructor
* @param _width The width the player should be when added to the stage.
* @param _height The height the player should be when added to the stage.
*/
public function VideoPlayer(_width:int = 640, _height:int = 360) {
…
}
Running
$ asdoc
-doc-sources=as/src
-output=doc/as
-compiler.library-path=as/swc
Generates…
Some things Flash can do that <video />
can’t:
Flash also has:
Open graph currently requires a self-contained SWF video player.
<meta name="og:video"
content="http://…/player.swf?…">
<meta name="og:video:type"
content="application/x-shockwave-flash">
<meta name="og:video:width" content="384">
<meta name="og:video:height" content="216">
Fire native YUI Custom Events directly from ActionScript.
var player = new Y.SWF(config);
player.on("videoplayer:adLoaded", function (e) {
// e.memo contains companion ad information
});
Call ExternalInterface
methods from JavaScript
player.callSWF("loadVideo", [video_id]);
Y.Base
that wrap JSON-based Web servicesY.Widget
state
, and a whole lot of events/**
* URL for more videos links on the hover, error and blocked
* states of the player.
* @attribute moreVideosURL
* @default "http://www.nfl.com/videos"
* @type String
* @writeOnce
*/
moreVideosURL: {
value: "http://www.nfl.com/videos",
writeOnce: true
},
The stack of sub-views all inherit from Y.Widget
.
THe stack of sub-views all inherit from Y.Widget
.
_uiRenderPlayer: function () {
// get config settings directly from the host
var host = this.get("host"),
widgetConfig = {
width: host.get("width"),
height: host.get("height"),
render: host.get("contentBox"),
visible: false
};
// initialize widget sub-views
this._errorFrame = new Video.ErrorFrame(widgetConfig);
this._loadingFrame = new Video.LoadingFrame(widgetConfig);
this._posterFrame = new Video.PosterFrame(widgetConfig);
this._endState = new Video.PlayerEndState(widgetConfig);
}
renderUI: function () {
var headlineContainer = Y.Node.create("<div></div>");
headlineContainer.addClass(this.getClassName("headline"));
this._headlineContainer = headlineContainer;
},
afterHeadlineChange: function (e) {
this._headlineContainer.setContent(e.newVal);
},
bindUI: function () {
this.after("headlineChange", this.afterHeadlineChange);
},
syncUI: function {
this.afterHeadlineChange({ newVal: this.get("headline") });
}
The skinnable
property of loader allows us to keep CSS modular.
"nfl-video-poster": {
requires: ["base-build", "widget-base"],
skinnable: true
}
Module asset structure:
nfl-video-poster/
assets/
skins/
sam/
nfl-video-poster.css
sprite.png
nfl-video-poster.js
Widget's getClassName
method allows for fast, semantic selectors.
.yui3-nfl-video-loading {
// …
}
.yui3-nfl-video-loading-hidden {
// …
}
.yui3-nfl-video-loading-content {
// …
}
.yui3-nfl-video-loading-messaging {
// …
}
.yui3-nfl-video-loading-spinner {
// …
}
Widget
s can be tested in isolation.The state
attribute governs the UI.
Video.VideoState = {
NONE: "none",
LOADING: "loading",
AD_PLAYING: "adplaying",
AD_PAUSED: "adpaused",
PLAYING: "playing",
PAUSED: "paused",
POSTER: "poster",
END: "end",
ERROR: "error"
};
The state
attribute governs the UI.
afterStateChange: function (e) {
// depending on what's currently showing, clean up
switch (e.prevVal) {
case VideoState.ERROR:
this._errorFrame.hide();
break;
// etc.
}
// set up what we want to show
switch (e.newVal) {
case VideoState.ERROR:
this._controls.hide();
this._errorFrame.show();
break;
// etc.
}
};
Events notify other modules that something has happened.
In Video.HTML5Player
:
this._videoNode.after("playing", function () {
this.fire(Video.PlayerEvent.PLAY);
}, this);
In Video.OmnitureTrackingController
:
player.after(PlayerEvent.PLAY, function () {
// track a video start
});
There are four ways to register an event listener:
Use the registration method highest on the list that matches your needs.
Leverage YUI’s custom event infrastructure to allow submodules to intercept user-initiated actions.
// publish a custom event for requesting a video to play
initializer: function () {
this.publish(HTML5VideoEvent.PLAY_REQUEST, {
defaultFn: this._defPlayRequestFn,
preventable: true
});
},
// If the event is successful, actually play the video
_defPlayRequestFn: function {
this.set(STATE, VideoState.PLAYING);
this._videoNode.play();
},
// public method only requests a play
play: function () {
this.fire(HTML5VideoEvent.PLAY_REQUEST);
}
Intercepting a play request.
player.on(HTML5VideoEvent.PLAY_REQUEST, function (e) {
if (shouldShowPreRoll) {
event.halt();
// load the pre-roll ad
}
});
Google’s IMA SDK is very similar to their Flash version. It’s a robust, well-documented API with great examples.
Things to be concerned about:
The standard way to load:
<script src="http://www.google.com/jsapi"></script>
<script>
google.setOnLoadCallback(onSdkLoaded);
google.load("ima", "1");
function onSdkLoaded() {
var adsLoader = new google.ima.AdsLoader();
}
</script>
YUI.add("google-ima", function (Y) {
Y.loadGoogleIMA = function (google) {
// google.ima library is loaded and ready
};
});
var eventName = "ima:loaded";
Y.loadGoogleIMA = function (cb) {
// set an event handler
var handle = Y.Global.once(eventName, function () {
if (Y.Lang.isFunction(cb)) {
cb(Y.config.win.google);
}
});
// load the library
// …
// return the handler
return handle;
};
If it doesn’t already exist, publish a custom event on Y.Global for when the library loads with fireOnce
set to true
, then load the library
var eventName = "ima:loaded";
function loadIMAOnce () {
// …
}
Y.loadGoogleIMA = function (cb) {
// set an event handler
// load the library
if (!Y.Global.hasEvent(eventName)) {
Y.Global.publish(eventName, { fireOnce: true });
loadIMAOnce();
}
// return the handler
};
Once the library is loaded, fire your custom event.
var eventName = "ima:loaded";
function loadIMAOnce () {
Y.Get.script("http://www.google.com/jsapi", {
onSuccess: googleLoaderReady
});
}
function googleLoaderReady() {
var google = Y.config.win.google;
google.load("ima", "1", {
callback: function () {
// the library is fully loaded
Y.Global.fire(eventName, google);
}
});
}
<style>
@font-face {
font-family: "NFLEndzoneSansMedium";
font-style: normal;
font-weight: normal;
src: url("//:") format("no404"),
url("assets/medium.eot?iefix") format("eot"),
url("assets/medium.woff") format("woff"),
url("assets/medium.ttf") format("truetype");
}
body {
font-family: NFLEndzoneSansMedium, sans-serif;
}
</style>
Has some drawbacks.
Use Loader for conditionally-loaded content.
var YUI_config = {
…
groups: {
nflui: {
…
modules: {
"font-endzone-sans": { type: "css" },
"nfl-video-controls": { requires: ["font-endzone-sans", … ] }
}
}
}
};
For static content, call YUI.add
.
<link rel="stylesheet"
href="http://…/font-endzone-sans/font-endzone-sans.css">
<script>
YUI.add("font-endzone-sans", function () {});
</script>
Add support for media events.
// Adding
Y.Node.DOM_EVENTS.playing = 1;
// enables
Y.one("video").on("playing", function (e) {
/* … */
});
…or just use the node-event-html5
module.
Allows you to call methods like play()
directly from a Node
instance.
if (!Y.Node.prototype.play) {
Y.Node.addMethod("play", function () {
// the Node instance is the first argument
var args = Y.Array(arguments),
node = args.shift();
// if the method exists, call it and return the results
if (Y.Lang.isFunction(node.play)) {
return node.play.apply(node, args);
}
});
}
We added support for play()
, pause()
, canPlayType()
,
requestFullScreen()
& cancelFullScreen()
.
Y.Node.SUPPORTS_FULLSCREEN = Y.Array.some([
// objects with properties test, isFullScreen,
// requestFullScreen, cancelFullScreen, and event
specAPI, webKitAPI, mozAPI
], function (api) {
var supported = api.test();
if (supported) {
// add api methods to Y.Node
}
return supported;
});
We built some awesome test pages with YTF.
-webkit-transform: translate3d(0, 0, 0)
is the new zoom: 1
.YUI({
groups: {
group1: {
conflict: {}
},
group2: {
conflict: {}
}
}
}).use("conflict", function (Y) {
// what code do I have?
});
Potential problem when providing a YUI-based web service.
<!-- Your code uses the latest -->
<script src="http://yui.yahooapis.com/3.7.3/build/yui/yui-min.js"></script>
<!-- The implementer relies on an earlier version -->
<script src="http://yui.yahooapis.com/3.4.0/build/yui/yui-min.js"></script>
<!-- The result -->
<script>
YUI().use("router", function (Y) {
/* Y.Controller is undefined */
});
</script>