/**
* The Video Component
*
* @module aui-video
*/
var Lang = A.Lang,
UA = A.UA,
getClassName = A.getClassName,
CSS_VIDEO_NODE = getClassName('video', 'node'),
DOC = A.config.doc,
TPL_VIDEO = '<video id="{id}" controls="controls" class="' + CSS_VIDEO_NODE + '"></video>';
/**
* A base class for Video.
*
* Check the [live demo](http://alloyui.com/examples/video/).
*
* @class A.Video
* @extends A.Component
* @param {Object} config Object literal specifying widget configuration
* properties.
* @constructor
* @example
```
<div id="myVideo"></div>
```
* @example
```
YUI().use(
'aui-video',
function(Y) {
new Y.Video(
{
boundingBox: '#myVideo',
ogvUrl: 'https://alloyui.com/video/movie.ogg',
url: 'https://alloyui.com/video/movie.mp4'
}
).render();
}
);
```
*/
var Video = A.Component.create({
/**
* Static property provides a string to identify the class.
*
* @property NAME
* @type String
* @static
*/
NAME: 'video',
/**
* Static property used to define the default attribute
* configuration for the Video.
*
* @property ATTRS
* @type Object
* @static
*/
ATTRS: {
/**
* An additional list of attributes.
*
* @attribute fixedAttributes
* @default {}
* @type Object
*/
fixedAttributes: {
value: {}
},
/**
* URL (on .ogv format) used by Video to play.
*
* @attribute ogvUrl
* @default ''
* @type String
*/
ogvUrl: {
value: ''
},
/**
* Image displayed before playback starts.
*
* @attribute poster
* @default ''
* @type String
*/
poster: {
value: ''
},
/**
* If `true` the render phase will be automatically invoked
* preventing the `.render()` manual call.
*
* @attribute render
* @default true
* @type Boolean
*/
render: {
value: true
},
/**
* Sets the `aria-role` for Video.
*
* @attribute role
* @default 'application'
* @type String
*/
role: {
validator: Lang.isString,
value: 'application',
writeOnce: 'initOnly'
},
/**
* URL used by Video to play.
*
* @attribute url
* @default ''
* @type String
*/
url: {
value: ''
},
/**
* Boolean indicating if use of the WAI-ARIA Roles and States
* should be enabled.
*
* @attribute useARIA
* @default true
* @type Boolean
*/
useARIA: {
validator: Lang.isBoolean,
value: true,
writeOnce: 'initOnly'
}
},
/**
* Static property used to define the attributes
* for the bindUI lifecycle phase.
*
* @property BIND_UI_ATTRS
* @type Array
* @static
*/
BIND_UI_ATTRS: ['url', 'poster', 'ogvUrl', 'fixedAttributes'],
/**
* Static property used to define the attributes
* for the syncUI lifecycle phase.
*
* @property SYNC_UI_ATTRS
* @type Array
* @static
*/
SYNC_UI_ATTRS: ['url', 'poster', 'ogvUrl'],
prototype: {
/**
* Destructor implementation.
* Lifecycle.
*
* @method destructor
* @protected
*/
destructor: function() {
var instance = this;
(new A.EventHandle(instance._eventHandles)).detach();
},
/**
* Render the Video component instance. Lifecycle.
*
* @method renderUI
* @protected
*/
renderUI: function() {
var instance = this;
instance._renderVideoTask = A.debounce(instance._renderVideo, 1, instance);
instance._renderVideo(!instance.get('ogvUrl'));
instance._video.on(
'play',
function (event) {
instance.fire(
'play',
{
cropType: event.type
}
);
}
);
instance._video.on(
'pause',
function (event) {
instance.fire(
'pause',
{
cropType: event.type
}
);
}
);
instance._setResponsiveDimensions();
},
/**
* Bind the events on the Video UI. Lifecycle.
*
* @method bindUI
* @protected
*/
bindUI: function() {
var instance = this;
instance.publish(
'videoReady',
{
fireOnce: true
}
);
instance.publish('play');
instance.publish('pause');
instance._eventHandles = [
A.after(
'windowresize',
A.bind('_afterWindowResize', instance)
)
];
},
/**
* Sync the Video UI. Lifecycle.
*
* @method syncUI
* @protected
*/
syncUI: function() {
var instance = this;
if (instance.get('useARIA')) {
instance.plug(
A.Plugin.Aria,
{
roleName: instance.get('role'),
roleNode: instance.get('contentBox')
}
);
}
},
/**
* Load video track.
*
* @method load
*/
load: function() {
var instance = this;
if (instance._video.hasMethod('load')) {
instance._video.invoke('load');
}
},
/**
* Pause video track.
*
* @method pause
*/
pause: function() {
var instance = this;
if (instance._video.hasMethod('pause')) {
instance._video.invoke('pause');
}
},
/**
* Play video track.
*
* @method play
*/
play: function() {
var instance = this;
if (instance._video.hasMethod('play')) {
instance._video.invoke('play');
}
},
/**
* Fired after the `windowresize` event.
*
* @method _afterWindowResize
* @protected
*/
_afterWindowResize: function() {
var instance = this;
instance._responsiveBoundingBox();
instance._setResponsiveDimensions();
},
/**
* Create `source` element
* using passed type attribute.
*
* @method _createSource
* @param type
* @protected
*/
_createSource: function(type) {
var sourceNode = new A.Node(DOC.createElement('source'));
sourceNode.attr('type', type);
return sourceNode;
},
/**
* Render Video in DOM.
*
* @method _renderVideo
* @protected
*/
_renderVideo: function() {
var instance,
height,
tpl,
tplObj,
video,
width;
instance = this;
tpl = TPL_VIDEO;
height = instance.get('height');
width = instance.get('width');
tplObj = Lang.sub(
tpl,
{
id: A.guid()
}
);
video = A.Node.create(tplObj);
if (width) {
video.width(width);
}
if (height) {
video.height(height);
}
instance.get('contentBox').append(video);
instance._video = video;
},
/**
* Remove the defined height and width from the bounding box.
*
* @method _responsiveBoundingBox
* @protected
*/
_responsiveBoundingBox: function() {
var instance = this,
boundingBox = instance.get('boundingBox');
boundingBox.setStyles(
{
height: '',
width: ''
}
);
},
/**
* Set the dimensions of the video player based on the window size.
*
* @method _setResponsiveDimensions
* @protected
*/
_setResponsiveDimensions: function() {
var instance,
aspectRatio,
currentTargetHeight,
currentTargetWidth,
height,
updatedHeight,
updatedWidth,
width,
winNode;
instance = this;
height = instance.get('height');
width = instance.get('width');
aspectRatio = height / width;
updatedHeight = height;
updatedWidth = width;
winNode = A.one(window);
currentTargetHeight = winNode.get('innerHeight');
if (currentTargetHeight < height) {
updatedHeight = currentTargetHeight;
updatedWidth = currentTargetHeight / aspectRatio;
}
currentTargetWidth = winNode.get('innerWidth');
if (currentTargetWidth < width) {
updatedHeight = currentTargetWidth * aspectRatio;
updatedWidth = currentTargetWidth;
}
instance._video.width(updatedWidth);
instance._video.height(updatedHeight);
},
/**
* Set the `ogvUrl` on the UI.
*
* @method _uiSetOgvUrl
* @param val
* @protected
*/
_uiSetOgvUrl: function(val) {
var instance = this;
if (UA.gecko || UA.opera) {
var video = instance._video;
var usingVideo = instance._usingVideo();
if ((!val && usingVideo) || (val && !usingVideo)) {
video.remove(true);
instance._renderVideoTask(!val);
}
if (val) {
var sourceOgv = instance._sourceOgv;
if (!sourceOgv) {
sourceOgv = instance._createSource('video/ogg; codecs="theora, vorbis"');
video.append(sourceOgv);
instance._sourceOgv = sourceOgv;
}
sourceOgv.attr('src', val);
}
}
},
/**
* Set the `poster` on the UI.
*
* @method _uiSetPoster
* @param val
* @protected
*/
_uiSetPoster: function(val) {
var instance = this;
var video = instance._video;
if (instance._usingVideo()) {
video.setAttribute('poster', val);
}
},
/**
* Set the `url` on the UI.
*
* @method _uiSetUrl
* @param val
* @protected
*/
_uiSetUrl: function(val) {
var instance = this;
var ogvUrl = instance.get('ogvUrl');
var video = instance._video;
var sourceMp4 = instance._sourceMp4;
if (UA.gecko && !instance._usingVideo()) {
if (sourceMp4) {
sourceMp4.remove(true);
instance._sourceMp4 = null;
}
}
else if (video || !ogvUrl) {
if (!sourceMp4) {
sourceMp4 = instance._createSource('video/mp4;');
video.append(sourceMp4);
instance._sourceMp4 = sourceMp4;
}
sourceMp4.attr('src', val);
}
},
/**
* Check if it's a `video` node.
*
* @method _usingVideo
* @protected
*/
_usingVideo: function() {
var instance = this;
return (instance._video.get('nodeName').toLowerCase() === 'video');
}
}
});
A.Video = Video;