'use strict'; /*@ Ox.VideoElement <f> VideoElement Object ([options[, self]]) -> <o:Ox.Element> VideoElement Object options <o> Options object self <o> shared private variable @*/ Ox.VideoElement = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ autoplay: false, preload: 'none', src: [] }) .options(options || {}) .css({width: '100%', height: '100%'}); Ox.Log('Video', 'VIDEO ELEMENT OPTIONS', self.options); self.currentPart = 0; self.items = []; self.loadedMetadata = false; self.paused = true; self.$video = $('<div>'); if (Ox.isFunction(self.options.src)) { self.isPlaylist = true; self.currentItem = 0; self.currentPage = 0; self.loadedMetadata = false; self.pageLength = 2; self.options.src(function(items) { self.numberOfItems = items; self.numberOfPages = Math.ceil(self.numberOfItems / self.pageLength); loadPages(function() { Ox.Log('Video', 'VIDEO PAGES LOADED'); setCurrentItem(0); if (!self.loadedMedatata) { self.loadedMetadata = true; that.triggerEvent('loadedmetadata'); that.triggerEvent('pointschange'); // fixme: needs to be triggered again, loadedmetadata messes with duration } }); }); } else { self.numberOfItems = 1; self.items.push(loadItem(self.options.src)); } function getCurrentPage() { return Math.floor(self.currentItem / self.pageLength); } function getCurrentTime() { return self.items[self.currentItem].offsets[self.currentPart] + self.video.currentTime; } function getset(key, value) { var ret; if (Ox.isUndefined(value)) { ret = self.video[key]; } else { self.video[key] = value; ret = that; } return ret; } function loadItem(src, points, callback) { src = Ox.isArray(src) ? src : [src]; var item = { currentPart: 0, duration: 0, durations: src.map(function() { return 0; }), offsets: [], parts: src.length }; if (points) { item.points = points; } item.$videos = src.map(function(src, i) { // in all browsers except firefox, // loadedmetadata fires only once per src if (Ox.startsWith(Ox.parseURL(src).protocol, 'http')) { src += '?' + Ox.uid(); } return $('<video>') .css({position: 'absolute'}) .on({ ended: function() { if (i < item.parts - 1) { setCurrentPart(self.currentPart + 1); self.video.play(); } /*else if (self.isPlaylist) { setCurrentItem(self.currentItem + 1); self.video.play(); }*/ else { self.ended = true; self.paused = true; that.triggerEvent('ended'); } }, loadedmetadata: function() { item.durations[i] = item.videos[i].duration; if (self.isPlaylist && i == 0) { item.videos[0].currentTime = item.points[0]; } if (Ox.every(item.durations)) { item.duration = Ox.sum(item.durations); item.offsets = Ox.range(item.parts).map(function(i) { return Ox.sum(item.durations.slice(0, i)); }); //Ox.Log('Video', 'METADATA OF', src, 'LOADED', item) if (self.isPlaylist) { callback && callback(); } else { setCurrentItem(0); self.loadedMetadata = true; that.triggerEvent('loadedmetadata'); } } }, progress: function() { // not implemented }, seeked: function() { that.triggerEvent('seeked'); }, seeking: function() { that.triggerEvent('seeking'); }, stop: function() { // custom event to be triggered on removal from the DOM if (self.video) { self.video.pause(); self.video.src = ''; } that.triggerEvent('ended'); } }) .attr(Ox.extend({ preload: 'metadata', src: src }, i == 0 && self.options.autoplay ? { autoplay: 'autoplay' } : {})) .hide() .appendTo(that.$element); }); item.videos = item.$videos.map(function($video) { return $video[0]; }); self.$brightness = $('<div>').css({ width: '100%', height: '100%', background: 'rgb(0, 0, 0)', opacity: 0 }) .appendTo(that.$element); return item; } function loadPage(page, callback) { Ox.Log('Video', 'VIDEO loadPage', page); //page = Ox.mod(page, self.numberOfPages); var loadedmetadata = 0, start = page * self.pageLength, stop = Math.min(start + self.pageLength, self.numberOfItems), pageLength = stop - start; if (!self.items[start]) { self.options.src([start, stop], function(data) { data.forEach(function(data, i) { self.items[start + i] = loadItem(data.parts, data.points, function(item) { if (++loadedmetadata == pageLength) { Ox.Log('Video', 'VIDEO page', page, 'loaded'); callback && callback(); } }); }); }); } else { Ox.Log('Video', 'PAGE IN CACHE'); callback && callback(); } } function loadPages(callback) { var currentPage = self.currentPage, nextPage = Ox.mod(currentPage + 1, self.numberOfPages), previousPage = Ox.mod(currentPage - 1, self.numberOfPages); loadPage(currentPage, function() { if (nextPage != currentPage) { loadPage(nextPage, function() { if (previousPage != currentPage && previousPage != nextPage) { unloadPage(previousPage); } }); } callback && callback(); }); } function setCurrentItem(item) { Ox.Log('Video', 'scI', item); var interval; item = Ox.mod(item, self.numberOfItems); self.video && self.video.pause(); if (self.items[item]) { set(); } else { that.triggerEvent('seeking'); interval = setInterval(function() { Ox.Log('Video', 'ITEM', item, 'NOT AVAILABLE'); if (self.items[item]) { clearInterval(interval); that.triggerEvent('seeked'); set(); } }, 250); } function set() { self.currentItem = item; self.currentPart = -1; setCurrentTime(0); if (self.isPlaylist) { that.triggerEvent('pointschange'); that.triggerEvent('sizechange'); self.currentPage = getCurrentPage(); loadPages(); } } } function setCurrentPart(part) { Ox.Log('Video', 'sCP', part); var css = {}, muted = false, volume = 1; ['left', 'top', 'width', 'height'].forEach(function(key) { css[key] = self.$video.css(key); }); if (self.video) { self.$video.hide(); self.video.pause(); self.video.currentTime = 0; volume = self.video.volume; muted = self.video.muted; } self.$video = self.items[self.currentItem].$videos[part].css(css).show(); self.video = self.$video[0]; self.video.volume = volume; self.video.muted = muted; !self.paused && self.video.play(); self.currentPart = part; Ox.Log('Video', 'sCP', part, self.video.src); } function setCurrentTime(time) { Ox.Log('Video', 'sCT', time); var currentPart, currentTime, item = self.items[self.currentItem]; Ox.loop(item.parts - 1, -1, -1, function(i) { if (item.offsets[i] <= time) { currentPart = i; currentTime = time - item.offsets[i]; Ox.Break(); } }); Ox.Log('Video', 'sCT', time, currentPart, currentTime); if (currentPart != self.currentPart) { setCurrentPart(currentPart); } self.video.currentTime = currentTime; } function unloadPage(page) { //page = Ox.mod(page, self.numberOfPages); Ox.Log('Video', 'unloadPage', page); var start = page * self.pageLength, stop = Math.min(start + self.pageLength, self.numberOfItems); Ox.range(start, stop).forEach(function(i) { if (self.items[i]) { self.items[i].$videos.forEach(function($video) { $video[0].src = ''; $video.remove(); }); delete self.items[i]; } }); } /*@ animate <f> animate @*/ that.animate = function() { self.$video.animate.apply(self.$video, arguments); return that; }; /*@ brightness <f> get/set brightness @*/ that.brightness = function() { var ret; if (arguments.length == 0) { ret = 1 - parseFloat(self.$brightness.css('opacity')); } else { self.$brightness.css({opacity: 1 - arguments[0]}); ret = that; } return ret; }; /*@ buffered <f> buffered @*/ that.buffered = function() { return self.video.buffered; }; /*@ currentTime <f> get/set currentTime @*/ that.currentTime = function() { var ret; self.ended = false; if (arguments.length == 0) { ret = getCurrentTime(); } else { setCurrentTime(arguments[0]); ret = that; } return ret; }; /*@ css <f> css @*/ that.css = function() { var interval; if (self.$video) { self.$video.css.apply(self.$video, arguments); } else { interval = setInterval(function() { Ox.Log('Video', 'VIDEO NOT YET AVAILABLE'); if (self.$video) { clearInterval(interval); self.$video.css.apply(self.$video, arguments); } }, 250); } return that; }; /*@ duration <f> duration @*/ that.duration = function() { // 86399 return self.items[self.currentItem].duration; }; /*@ muted <f> get/set muted @*/ that.muted = function() { return getset('muted', arguments[0]); }; /*@ points <f> get points @*/ that.points = function() { return self.items[self.currentItem].points; }; /*@ pause <f> pause @*/ that.pause = function() { self.paused = true; self.video.pause(); return that; }; /*@ play <f> play @*/ that.play = function() { if (self.ended) { that.currentTime(0); self.ended = false; } self.paused = false; self.video.play(); return that; }; /*@ playNext <f> play next @*/ that.playNext = function() { Ox.Log('Video', 'PLAY NEXT'); setCurrentItem(self.currentItem + 1); self.video.play(); }; /*@ playPrevious <f> play previous @*/ that.playPrevious = function() { setCurrentItem(self.currentItem - 1); }; /*@ src <f> get/set src @*/ that.src = function() { var ret; if (arguments.length == 0) { ret = self.video.src; } else { self.options.src = Ox.isArray(arguments[0]) ? arguments[0] : [arguments[0]]; if (self.loadedMetadata) { self.$video[self.currentPart].src = self.options.src[self.currentPart]; self.$video.each(function(video, i) { if (i != self.currentPart) { var src = self.options.src[i]; if (Ox.startsWith(Ox.parseURL(src).protocol, 'http')) { src += '?' + Ox.uid(); } video.src = src; } }); } else { self.items[0].$videos.forEach(function($video, i) { var src = self.options.src[i]; if (Ox.startsWith(Ox.parseURL(src).protocol, 'http')) { src += '?' + Ox.uid(); } $video[0].src = src; }); } ret = that; } return ret; }; /*@ videoHeight <f> get videoHeight @*/ that.videoHeight = function() { return self.video.videoHeight; }; /*@ videoWidth <f> get videoWidth @*/ that.videoWidth = function() { return self.video.videoWidth; }; /*@ volume <f> get/set volume @*/ that.volume = function(value) { return getset('volume', arguments[0]); }; return that; };