699 lines
22 KiB
JavaScript
699 lines
22 KiB
JavaScript
'use strict';
|
|
var extend = require('extend-object'),
|
|
isFunction = require('is-function'),
|
|
bean = require('bean'),
|
|
slider = require('./ext/ui/slider'),
|
|
barSlider = require('./ext/ui/bar-slider'),
|
|
common = require('./common'),
|
|
events = require('./ext/events');
|
|
|
|
var instances = [],
|
|
extensions = [];
|
|
|
|
|
|
var oldHandler = window.onbeforeunload;
|
|
window.onbeforeunload = function(ev) {
|
|
instances.forEach(function(api) {
|
|
if (api.conf.splash) {
|
|
api.unload();
|
|
} else {
|
|
api.bind("error", function () {
|
|
common.find('.flowplayer.is-error .fp-message').forEach(common.removeNode);
|
|
});
|
|
}
|
|
});
|
|
if (oldHandler) return oldHandler(ev);
|
|
};
|
|
|
|
var isSafari = /Safari/.exec(navigator.userAgent) && !/Chrome/.exec(navigator.userAgent),
|
|
m = /(\d+\.\d+) Safari/.exec(navigator.userAgent),
|
|
safariVersion = m ? Number(m[1]) : 100;
|
|
|
|
/* flowplayer() */
|
|
var flowplayer = module.exports = function(fn, opts, callback) {
|
|
if (isFunction(fn)) return extensions.push(fn);
|
|
if (typeof fn == 'number' || typeof fn === 'undefined') return instances[fn || 0];
|
|
if (fn.nodeType) { // Is an element
|
|
if (fn.getAttribute('data-flowplayer-instance-id') !== null) { // Already flowplayer instance
|
|
return instances[fn.getAttribute('data-flowplayer-instance-id')];
|
|
}
|
|
if (!opts) return; // Can't initialize without data
|
|
return initializePlayer(fn, opts, callback);
|
|
}
|
|
if (fn.jquery) return flowplayer(fn[0], opts, callback);
|
|
if (typeof fn === 'string') {
|
|
var el = common.find(fn)[0];
|
|
return el && flowplayer(el, opts, callback);
|
|
}
|
|
};
|
|
|
|
extend(flowplayer, {
|
|
|
|
version: '@VERSION',
|
|
|
|
engines: [],
|
|
|
|
engine: function(name) {
|
|
return flowplayer.engines.filter(function(e) { return e.engineName === name; })[0];
|
|
},
|
|
|
|
extensions: [],
|
|
|
|
conf: {},
|
|
|
|
set: function(key, value) {
|
|
if (typeof key === 'string') flowplayer.conf[key] = value;
|
|
else extend(flowplayer.conf, key);
|
|
},
|
|
|
|
registerExtension: function(js, css) {
|
|
flowplayer.extensions.push([js, css]);
|
|
},
|
|
|
|
support: {},
|
|
|
|
defaults: {
|
|
|
|
debug: false,
|
|
|
|
// true = forced playback
|
|
disabled: false,
|
|
|
|
fullscreen: window == window.top,
|
|
|
|
// keyboard shortcuts
|
|
keyboard: true,
|
|
|
|
// default aspect ratio
|
|
ratio: 9 / 16,
|
|
|
|
adaptiveRatio: false,
|
|
|
|
rtmp: 0,
|
|
|
|
proxy: 'best',
|
|
|
|
hlsQualities: true,
|
|
|
|
seekStep: false,
|
|
|
|
splash: false,
|
|
|
|
live: false,
|
|
livePositionOffset: 120,
|
|
|
|
swf: "//@CDN/@VERSION/@CDN_PATHflowplayer.swf",
|
|
swfHls: "//@CDN/@VERSION/@CDN_PATHflowplayerhls.swf",
|
|
|
|
speeds: [0.25, 0.5, 1, 1.5, 2],
|
|
|
|
tooltip: true,
|
|
|
|
mouseoutTimeout: 5000,
|
|
|
|
mutedAutoplay: true,
|
|
|
|
clickToUnMute: true,
|
|
|
|
// initial volume level
|
|
volume: 1,
|
|
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes
|
|
errors: [
|
|
|
|
// video exceptions
|
|
'',
|
|
'Video loading aborted',
|
|
'Network error',
|
|
'Video not properly encoded',
|
|
'Video file not found',
|
|
|
|
// player exceptions
|
|
'Unsupported video',
|
|
'Skin not found',
|
|
'SWF file not found',
|
|
'Subtitles not found',
|
|
'Invalid RTMP URL',
|
|
'Unsupported video format. Try installing Adobe Flash.'
|
|
],
|
|
errorUrls: ['','','','','','','','','','',
|
|
'http://get.adobe.com/flashplayer/'
|
|
],
|
|
playlist: [],
|
|
|
|
hlsFix: isSafari && safariVersion < 8,
|
|
|
|
disableInline: false
|
|
|
|
},
|
|
// Expose utilities for plugins
|
|
bean: bean,
|
|
common: common,
|
|
slider: slider,
|
|
barSlider: barSlider,
|
|
extend: extend
|
|
|
|
|
|
|
|
});
|
|
|
|
// keep track of players
|
|
var playerCount = 0;
|
|
|
|
var URLResolver = require('./ext/resolve');
|
|
|
|
|
|
|
|
if (typeof window.jQuery !== 'undefined') {
|
|
var $ = window.jQuery;
|
|
// auto-install (any video tag with parent .flowplayer)
|
|
$(function() {
|
|
if (typeof $.fn.flowplayer == 'function') {
|
|
$('.flowplayer:has(video:not(.fp-engine),script[type="application/json"])').flowplayer();
|
|
}
|
|
});
|
|
|
|
// jQuery plugin
|
|
var videoTagConfig = function(videoTag) {
|
|
if (!videoTag.length) return {};
|
|
var clip = videoTag.data() || {}, conf = {};
|
|
$.each(['autoplay', 'loop', 'preload', 'poster'], function(i, key) {
|
|
var val = videoTag.attr(key);
|
|
if (val !== undefined && ['autoplay', 'poster'].indexOf(key) !== -1) conf[key] = val ? val : true;
|
|
else if (val !== undefined) clip[key] = val ? val : true;
|
|
});
|
|
videoTag[0].autoplay = videoTag[0].preload = false;
|
|
clip.subtitles = videoTag.find('track').map(function() {
|
|
var tr = $(this);
|
|
return {
|
|
src: tr.attr('src'),
|
|
kind: tr.attr('kind'),
|
|
label: tr.attr('label'),
|
|
srclang: tr.attr('srclang'),
|
|
'default': tr.prop('default')
|
|
};
|
|
}).get();
|
|
|
|
clip.sources = (new URLResolver()).sourcesFromVideoTag(videoTag, $);
|
|
return extend(conf, {clip: clip});
|
|
};
|
|
$.fn.flowplayer = function(opts, callback) {
|
|
return this.each(function() {
|
|
if (typeof opts == 'string') opts = { swf: opts };
|
|
if (isFunction(opts)) { callback = opts; opts = {}; }
|
|
var root = $(this),
|
|
scriptConf = root.find('script[type="application/json"]'),
|
|
confObject = scriptConf.length ? JSON.parse(scriptConf.text()) : videoTagConfig(root.find('video')),
|
|
conf = $.extend({}, opts || {}, confObject, root.data());
|
|
var api = initializePlayer(this, conf, callback);
|
|
events.EVENTS.forEach(function(evName) {
|
|
api.on(evName + '.jquery', function(ev) {
|
|
root.trigger.call(root, ev.type, ev.detail && ev.detail.args);
|
|
});
|
|
});
|
|
root.data('flowplayer', api);
|
|
});
|
|
};
|
|
}
|
|
|
|
function initializePlayer(element, opts, callback) {
|
|
if (opts && opts.embed) opts.embed = extend({}, flowplayer.defaults.embed, opts.embed);
|
|
|
|
var supportLocalStorage = false;
|
|
try {
|
|
if (typeof flowplayer.conf.storage === 'undefined' && typeof window.localStorage == "object") {
|
|
window.localStorage.flowplayerTestStorage = "test";
|
|
supportLocalStorage = true;
|
|
}
|
|
} catch (ignored) {}
|
|
|
|
|
|
|
|
var root = element,
|
|
conf = extend({}, flowplayer.defaults, flowplayer.conf, opts),
|
|
storage = {},
|
|
originalClass = root.className,
|
|
lastSeekPosition,
|
|
engine,
|
|
urlResolver = new URLResolver();
|
|
|
|
common.addClass(root, 'is-loading');
|
|
common.toggleClass(root, 'no-flex', !flowplayer.support.flex);
|
|
common.toggleClass(root, 'no-svg', !flowplayer.support.svg);
|
|
try {
|
|
storage = flowplayer.conf.storage || (supportLocalStorage ? window.localStorage : storage);
|
|
} catch(e) {}
|
|
|
|
conf.volume = storage.muted === "true" ? 0 : conf.volume !== flowplayer.defaults.volume ? conf.volume : !isNaN(storage.volume) ? storage.volume : conf.volume;
|
|
|
|
conf.debug = !!storage.flowplayerDebug || conf.debug;
|
|
|
|
if (conf.aspectRatio && typeof conf.aspectRatio === 'string') {
|
|
var parts = conf.aspectRatio.split(/[:\/]/);
|
|
conf.ratio = parts[1] / parts[0];
|
|
}
|
|
|
|
var isRTL = (root.currentStyle && root.currentStyle.direction === 'rtl') ||
|
|
(window.getComputedStyle && window.getComputedStyle(root, null) !== null && window.getComputedStyle(root, null).getPropertyValue('direction') === 'rtl');
|
|
|
|
if (isRTL) common.addClass(root, 'is-rtl');
|
|
|
|
/*** API ***/
|
|
var api = {
|
|
|
|
// properties
|
|
conf: conf,
|
|
currentSpeed: 1,
|
|
volumeLevel: conf.muted ? 0 : typeof conf.volume === "undefined" ? storage.volume * 1 : conf.volume,
|
|
video: {},
|
|
|
|
// states
|
|
disabled: false,
|
|
finished: false,
|
|
loading: false,
|
|
muted: storage.muted == "true" || conf.muted,
|
|
paused: false,
|
|
playing: false,
|
|
ready: false,
|
|
splash: false,
|
|
rtl: isRTL,
|
|
|
|
// methods
|
|
//
|
|
hijack: function(hijack) {
|
|
try {
|
|
api.engine.suspendEngine();
|
|
} catch (e) { /* */ }
|
|
api.hijacked = hijack;
|
|
},
|
|
release: function() {
|
|
try {
|
|
api.engine.resumeEngine();
|
|
} catch (e) { /* */ }
|
|
api.hijacked = false;
|
|
},
|
|
debug: function() {
|
|
if (!conf.debug) return;
|
|
console.log.apply(console, ['DEBUG'].concat([].slice.call(arguments)));
|
|
},
|
|
load: function(video, callback) {
|
|
|
|
if (api.error || api.loading) return;
|
|
api.video = {};
|
|
|
|
api.finished = false;
|
|
|
|
video = video || conf.clip;
|
|
|
|
// resolve URL
|
|
video = extend({}, urlResolver.resolve(video, conf.clip.sources));
|
|
if (api.playing || api.engine) video.autoplay = true;
|
|
var engineImpl = selectEngine(video);
|
|
if (!engineImpl) return setTimeout(function() { api.trigger("error", [api, { code: flowplayer.support.flashVideo ? 5 : 10 }]); }) && api;
|
|
if (!engineImpl.engineName) throw new Error('engineName property of factory should be exposed');
|
|
if (!api.engine || engineImpl.engineName !== api.engine.engineName) {
|
|
api.ready = false;
|
|
if (api.engine) {
|
|
api.engine.unload();
|
|
api.conf.autoplay = true;
|
|
}
|
|
engine = api.engine = engineImpl(api, root);
|
|
api.one('ready', function() {
|
|
setTimeout(function() {
|
|
if (api.muted) api.mute(true, true);
|
|
else engine.volume(api.volumeLevel);
|
|
});
|
|
});
|
|
}
|
|
|
|
extend(video, engine.pick(video.sources.filter(function(source) { // Filter out sources explicitly configured for some other engine
|
|
if (!source.engine) return true;
|
|
return source.engine === engine.engineName;
|
|
})));
|
|
|
|
if (video.src) {
|
|
var e = api.trigger('load', [api, video, engine], true);
|
|
if (!e.defaultPrevented) {
|
|
api.ready = false;
|
|
engine.load(video);
|
|
|
|
// callback
|
|
if (isFunction(video)) callback = video;
|
|
if (callback) api.one("ready", callback);
|
|
} else {
|
|
api.loading = false;
|
|
}
|
|
}
|
|
|
|
return api;
|
|
},
|
|
|
|
pause: function(fn) {
|
|
if (api.hijacked) return api.hijacked.pause(fn) | api;
|
|
|
|
if (api.ready && !api.seeking && !api.loading) {
|
|
engine.pause();
|
|
api.one("pause", fn);
|
|
}
|
|
return api;
|
|
},
|
|
|
|
resume: function() {
|
|
var ev = api.trigger('beforeresume', [api], true);
|
|
if (ev.defaultPrevented) return;
|
|
if (api.hijacked) return api.hijacked.resume() | api;
|
|
|
|
if (api.ready && api.paused) {
|
|
engine.resume();
|
|
|
|
// Firefox (+others?) does not fire "resume" after finish
|
|
if (api.finished) {
|
|
api.trigger("resume", [api]);
|
|
api.finished = false;
|
|
}
|
|
}
|
|
|
|
return api;
|
|
},
|
|
|
|
toggle: function() {
|
|
return api.ready ? api.paused ? api.resume() : api.pause() : api.load();
|
|
},
|
|
|
|
/*
|
|
seek(1.4) -> 1.4s time
|
|
seek(true) -> 10% forward
|
|
seek(false) -> 10% backward
|
|
*/
|
|
seek: function(time, callback) {
|
|
if (typeof time == "boolean") {
|
|
var delta = api.conf.seekStep || api.video.duration * 0.1;
|
|
time = api.video.time + (time ? delta : -delta);
|
|
time = Math.min(Math.max(time, 0), api.video.duration - 0.1);
|
|
}
|
|
if (typeof time === 'undefined') return api;
|
|
if (api.hijacked) return api.hijacked.seek(time, callback) | api;
|
|
if (api.ready) {
|
|
lastSeekPosition = time;
|
|
var ev = api.trigger('beforeseek', [api, time], true);
|
|
if (!ev.defaultPrevented) {
|
|
engine.seek(time);
|
|
if (isFunction(callback)) api.one("seek", callback);
|
|
} else {
|
|
api.seeking = false;
|
|
common.toggleClass(root, 'is-seeking', api.seeking); // remove loading indicator
|
|
}
|
|
}
|
|
return api;
|
|
},
|
|
|
|
/*
|
|
seekTo(1) -> 10%
|
|
seekTo(2) -> 20%
|
|
seekTo(3) -> 30%
|
|
...
|
|
seekTo() -> last position
|
|
*/
|
|
seekTo: function(position, fn) {
|
|
if (position === undefined) return api.seek(lastSeekPosition, fn);
|
|
if (api.video.seekOffset !== undefined) { // Live stream
|
|
return api.seek(api.video.seekOffset + (api.video.duration - api.video.seekOffset) * 0.1 * position, fn);
|
|
}
|
|
return api.seek(api.video.duration * 0.1 * position, fn);
|
|
},
|
|
|
|
mute: function(flag, skipStore) {
|
|
if (flag === undefined) flag = !api.muted;
|
|
api.muted = flag;
|
|
if (!skipStore) {
|
|
storage.muted = flag;
|
|
storage.volume = !isNaN(storage.volume) ? storage.volume : conf.volume; // make sure storage has volume
|
|
}
|
|
if (typeof engine.mute !== 'undefined') engine.mute(flag);
|
|
else {
|
|
api.volume(flag ? 0 : storage.volume, true);
|
|
api.trigger("mute", [api, flag]);
|
|
}
|
|
return api;
|
|
},
|
|
|
|
volume: function(level, skipStore) {
|
|
if (api.ready) {
|
|
level = Math.min(Math.max(level, 0), 1);
|
|
if (!skipStore) storage.volume = level;
|
|
engine.volume(level);
|
|
}
|
|
|
|
return api;
|
|
},
|
|
|
|
speed: function(val, callback) {
|
|
|
|
if (api.ready) {
|
|
|
|
// increase / decrease
|
|
if (typeof val == "boolean") {
|
|
val = conf.speeds[conf.speeds.indexOf(api.currentSpeed) + (val ? 1 : -1)] || api.currentSpeed;
|
|
}
|
|
|
|
engine.speed(val);
|
|
if (callback) root.one("speed", callback);
|
|
}
|
|
|
|
return api;
|
|
},
|
|
|
|
|
|
stop: function() {
|
|
if (api.ready) {
|
|
api.pause();
|
|
if (!api.live || api.dvr) {
|
|
api.seek(0, function() {
|
|
api.trigger("stop", [api]);
|
|
});
|
|
} else {
|
|
api.trigger("stop", [api]);
|
|
}
|
|
}
|
|
return api;
|
|
},
|
|
|
|
unload: function() {
|
|
|
|
if (conf.splash) {
|
|
api.trigger("unload", [api]);
|
|
if (engine) {
|
|
engine.unload();
|
|
api.engine = engine = 0;
|
|
}
|
|
} else {
|
|
api.stop();
|
|
}
|
|
return api;
|
|
},
|
|
|
|
shutdown: function() {
|
|
api.unload();
|
|
api.trigger('shutdown', [api]);
|
|
bean.off(root);
|
|
delete instances[root.getAttribute('data-flowplayer-instance-id')];
|
|
root.removeAttribute('data-flowplayer-instance-id');
|
|
},
|
|
|
|
disable: function(flag) {
|
|
if (flag === undefined) flag = !api.disabled;
|
|
|
|
if (flag != api.disabled) {
|
|
api.disabled = flag;
|
|
api.trigger("disable", flag);
|
|
}
|
|
return api;
|
|
},
|
|
|
|
registerExtension: function(jsUrls, cssUrls) {
|
|
jsUrls = jsUrls || [];
|
|
cssUrls = cssUrls || [];
|
|
if (typeof jsUrls === 'string') jsUrls = [jsUrls];
|
|
if (typeof cssUrls === 'string') cssUrls = [cssUrls];
|
|
jsUrls.forEach(function(url) { api.extensions.js.push(url); });
|
|
cssUrls.forEach(function(url) { api.extensions.css.push(url); });
|
|
}
|
|
|
|
};
|
|
|
|
api.conf = extend(api.conf, conf);
|
|
api.extensions = { js: [], css: [] };
|
|
flowplayer.extensions.forEach(function(i) {
|
|
api.registerExtension(i[0], i[1]);
|
|
});
|
|
/* event binding / unbinding */
|
|
events(api);
|
|
|
|
var selectEngine = function(clip) {
|
|
var engine;
|
|
var engines = flowplayer.engines;
|
|
if (conf.engine) {
|
|
var eng = engines.filter(function(e) { return e.engineName === conf.engine; })[0];
|
|
if (eng && clip.sources.some(function(source) {
|
|
if (source.engine && source.engine !== eng.engineName) return false;
|
|
return eng.canPlay(source.type, api.conf);
|
|
})) return eng;
|
|
}
|
|
if (conf.enginePreference) engines = flowplayer.engines.filter(function(one) { return conf.enginePreference.indexOf(one.engineName) > -1; }).sort(function(a, b) {
|
|
return conf.enginePreference.indexOf(a.engineName) - conf.enginePreference.indexOf(b.engineName);
|
|
});
|
|
clip.sources.some(function(source) {
|
|
var eng = engines.filter(function(engine) {
|
|
if (source.engine && source.engine !== engine.engineName) return false;
|
|
return engine.canPlay(source.type, api.conf);
|
|
}).shift();
|
|
if (eng) engine = eng;
|
|
return !!eng;
|
|
});
|
|
return engine;
|
|
};
|
|
|
|
/*** Behaviour ***/
|
|
if (!root.getAttribute('data-flowplayer-instance-id')) { // Only bind once
|
|
root.setAttribute('data-flowplayer-instance-id', playerCount++);
|
|
|
|
|
|
api.on('boot', function() {
|
|
var support = flowplayer.support;
|
|
|
|
// splash
|
|
if (conf.splash || common.hasClass(root, "is-splash") ||
|
|
!support.firstframe) {
|
|
api.forcedSplash = !conf.splash && !common.hasClass(root, "is-splash");
|
|
api.splash = true;
|
|
if (!conf.splash) conf.splash = true;
|
|
common.addClass(root, "is-splash");
|
|
}
|
|
|
|
if (conf.splash) common.find('video', root).forEach(common.removeNode);
|
|
|
|
if (conf.dvr || conf.live || common.hasClass(root, 'is-live')) {
|
|
api.live = conf.live = true;
|
|
api.dvr = conf.dvr = !!conf.dvr || common.hasClass(root, 'is-dvr');
|
|
common.addClass(root, 'is-live');
|
|
common.toggleClass(root, 'is-dvr', api.dvr);
|
|
}
|
|
|
|
// extensions
|
|
extensions.forEach(function(e) {
|
|
e(api, root);
|
|
});
|
|
|
|
// instances
|
|
instances.push(api);
|
|
|
|
// start
|
|
if (conf.splash) api.unload(); else api.load();
|
|
|
|
// disabled
|
|
if (conf.disabled) api.disable();
|
|
|
|
// initial callback
|
|
api.one("ready", callback);
|
|
|
|
api.one('shutdown', function() { root.className = originalClass; });
|
|
|
|
|
|
}).on("load", function(e, api, video) {
|
|
|
|
// unload others
|
|
if (conf.splash) {
|
|
common.find('.flowplayer.is-ready,.flowplayer.is-loading').forEach(function(el) {
|
|
var playerId = el.getAttribute('data-flowplayer-instance-id');
|
|
if (playerId === root.getAttribute('data-flowplayer-instance-id')) return;
|
|
var a = instances[Number(playerId)];
|
|
if (a && a.conf.splash) a.unload();
|
|
});
|
|
|
|
}
|
|
|
|
// loading
|
|
common.addClass(root, "is-loading");
|
|
api.loading = true;
|
|
|
|
if (typeof video.live !== 'undefined' || typeof video.dvr !== 'undefined') {
|
|
common.toggleClass(root, 'is-live', video.dvr || video.live);
|
|
common.toggleClass(root, 'is-dvr', !!video.dvr);
|
|
api.live = video.dvr || video.live;
|
|
api.dvr = !!video.dvr;
|
|
}
|
|
|
|
|
|
}).on("ready", function(e, api, video) {
|
|
video.time = 0;
|
|
api.video = video;
|
|
|
|
common.removeClass(root, "is-loading");
|
|
api.loading = false;
|
|
|
|
// saved state
|
|
if (api.muted) api.mute(true, true);
|
|
else api.volume(api.volumeLevel);
|
|
|
|
// see https://github.com/flowplayer/flowplayer/issues/479
|
|
|
|
var hlsFix = api.conf.hlsFix && /mpegurl/i.exec(video.type);
|
|
common.toggleClass(root, 'hls-fix', !!hlsFix);
|
|
|
|
}).on("unload", function() {
|
|
common.removeClass(root, "is-loading");
|
|
api.loading = false;
|
|
|
|
|
|
}).on("ready unload", function(e) {
|
|
var is_ready = e.type == "ready";
|
|
common.toggleClass(root, 'is-splash', !is_ready);
|
|
common.toggleClass(root, 'is-ready', is_ready);
|
|
api.ready = is_ready;
|
|
api.splash = !is_ready;
|
|
|
|
|
|
}).on("progress", function(e, api, time) {
|
|
api.video.time = time;
|
|
}).on('buffer', function(e, api, buffered) {
|
|
api.video.buffer = typeof buffered === 'number' ? buffered : buffered.length ? buffered[buffered.length - 1].end : 0;
|
|
}).on("speed", function(e, api, val) {
|
|
api.currentSpeed = val;
|
|
|
|
}).on("volume", function(e, api, level) {
|
|
api.volumeLevel = Math.round(level * 100) / 100;
|
|
if (api.muted && level) api.mute(false);
|
|
|
|
|
|
}).on("beforeseek seek", function(e) {
|
|
api.seeking = e.type == "beforeseek";
|
|
common.toggleClass(root, 'is-seeking', api.seeking);
|
|
|
|
}).on("ready pause resume unload finish stop", function(e) {
|
|
|
|
// PAUSED: pause / finish
|
|
api.paused = /pause|finish|unload|stop/.test(e.type);
|
|
api.paused = api.paused || e.type === 'ready' && !conf.autoplay && !api.playing;
|
|
|
|
// the opposite
|
|
api.playing = !api.paused;
|
|
|
|
// CSS classes
|
|
common.toggleClass(root, 'is-paused', api.paused);
|
|
common.toggleClass(root, 'is-playing', api.playing);
|
|
|
|
// sanity check
|
|
if (!api.load.ed) api.pause();
|
|
|
|
}).on("finish", function() {
|
|
api.finished = true;
|
|
|
|
}).on("error", function() {
|
|
});
|
|
}
|
|
|
|
// boot
|
|
api.trigger('boot', [api, root]);
|
|
return api;
|
|
}
|