oxjs/source/UI/js/Video/VideoElement.js

669 lines
22 KiB
JavaScript
Raw Permalink Normal View History

2011-11-05 16:46:53 +00:00
'use strict';
2011-09-14 14:34:33 +00:00
/*@
2012-05-31 10:32:54 +00:00
Ox.VideoElement <f> VideoElement Object
options <o> Options object
2013-07-09 22:48:22 +00:00
autoplay <b|false> autoplay
items <a|[]> array of objects with src,in,out,duration
2014-03-13 18:53:02 +00:00
loop <b|false> loop playback
2016-03-29 12:26:04 +00:00
playbackRate <n|1> playback rate
self <o> Shared private variable
2012-05-31 10:32:54 +00:00
([options[, self]]) -> <o:Ox.Element> VideoElement Object
2012-06-17 22:38:26 +00:00
loadedmetadata <!> loadedmetadata
2013-07-09 22:48:22 +00:00
itemchange <!> itemchange
2012-06-17 22:38:26 +00:00
seeked <!> seeked
seeking <!> seeking
sizechange <!> sizechange
ended <!> ended
2011-09-14 14:34:33 +00:00
@*/
2016-07-26 16:55:55 +00:00
(function() {
var queue = [],
queueSize = 100,
restrictedElements = [],
requiresUserGesture = mediaPlaybackRequiresUserGesture(),
unblock = [];
2011-04-22 22:03:10 +00:00
Ox.VideoElement = function(options, self) {
self = self || {};
2011-08-19 10:45:36 +00:00
var that = Ox.Element({}, self)
2012-12-09 00:42:57 +00:00
.defaults({
autoplay: false,
2013-07-09 22:48:22 +00:00
loop: false,
2021-08-04 12:42:10 +00:00
muted: false,
2016-03-29 12:26:04 +00:00
playbackRate: 1,
2021-08-04 12:17:27 +00:00
items: [],
volume: 1
2012-12-09 00:42:57 +00:00
})
.options(options || {})
2013-07-09 22:48:22 +00:00
.update({
items: function() {
self.loadedMetadata = false;
loadItems(function() {
self.loadedMetadata = true;
var update = true;
2013-07-13 23:06:07 +00:00
if (self.currentItem >= self.numberOfItems) {
self.currentItem = 0;
}
if (!self.numberOfItems) {
self.video.src = '';
that.triggerEvent('durationchange', {
duration: that.duration()
});
} else {
if (self.currentItemId != self.items[self.currentItem].id) {
// check if current item is in new items
self.items.some(function(item, i) {
if (item.id == self.currentItemId) {
self.currentItem = i;
loadNextVideo();
update = false;
return true;
}
});
if (update) {
self.currentItem = 0;
self.currentItemId = self.items[self.currentItem].id;
}
2013-07-09 22:48:22 +00:00
}
if (!update) {
that.triggerEvent('seeked');
that.triggerEvent('durationchange', {
duration: that.duration()
});
2014-02-13 06:47:10 +00:00
} else {
setCurrentVideo(function() {
that.triggerEvent('seeked');
that.triggerEvent('durationchange', {
duration: that.duration()
});
});
}
2013-07-09 22:48:22 +00:00
}
});
2016-03-29 12:26:04 +00:00
},
playbackRate: function() {
self.video.playbackRate = self.options.playbackRate;
2013-07-09 22:48:22 +00:00
}
})
2012-12-09 00:42:57 +00:00
.css({width: '100%', height: '100%'});
2011-08-19 10:45:36 +00:00
Ox.Log('Video', 'VIDEO ELEMENT OPTIONS', self.options);
2011-08-19 10:45:36 +00:00
2013-07-09 22:48:22 +00:00
self.currentItem = 0;
self.currentTime = 0;
self.currentVideo = 0;
2015-03-19 14:21:32 +00:00
self.items = [];
self.loadedMetadata = false;
2011-08-19 10:45:36 +00:00
self.paused = true;
2013-07-09 22:48:22 +00:00
self.seeking = false;
self.loading = true;
self.buffering = true;
2013-07-09 22:48:22 +00:00
self.$videos = [getVideo(), getVideo()];
self.$video = self.$videos[self.currentVideo];
self.video = self.$video[0];
2021-08-04 12:17:27 +00:00
self.volume = self.options.volume;
2021-08-04 12:42:10 +00:00
self.muted = self.options.muted;
2013-07-09 22:48:22 +00:00
self.$brightness = $('<div>').css({
width: '100%',
height: '100%',
background: 'rgb(0, 0, 0)',
opacity: 0
})
.appendTo(that);
self.timeupdate = setInterval(function() {
if (!self.paused
&& !self.loading
&& self.loadedMetadata
&& self.items[self.currentItem]
&& self.items[self.currentItem].out
&& self.video.currentTime >= self.items[self.currentItem].out) {
setCurrentItem(self.currentItem + 1);
}
}, 30);
2011-08-20 09:48:28 +00:00
// mobile browsers only allow playing media elements after user interaction
2016-07-26 16:55:55 +00:00
if (restrictedElements.length > 0) {
unblock.push(setSource);
setTimeout(function() {
that.triggerEvent('requiresusergesture');
})
} else {
setSource();
}
2011-04-22 22:03:10 +00:00
2011-08-19 10:45:36 +00:00
function getCurrentTime() {
2013-07-09 22:48:22 +00:00
var item = self.items[self.currentItem];
return self.seeking || self.loading
2013-07-09 22:48:22 +00:00
? self.currentTime
: item ? item.position + self.video.currentTime - item['in'] : 0;
2011-04-22 22:03:10 +00:00
}
2011-08-19 10:45:36 +00:00
function getset(key, value) {
var ret;
if (Ox.isUndefined(value)) {
ret = self.video[key];
2011-08-19 10:45:36 +00:00
} else {
self.video[key] = value;
ret = that;
2011-04-22 22:03:10 +00:00
}
2011-08-19 10:45:36 +00:00
return ret;
}
2013-07-09 22:48:22 +00:00
function getVideo() {
2016-07-26 16:55:55 +00:00
return getVideoElement()
2013-07-09 22:48:22 +00:00
.css({position: 'absolute'})
.on({
ended: function() {
if (self.video == this) {
setCurrentItem(self.currentItem + 1);
}
},
loadedmetadata: function() {
// metadata loaded in loadItems
},
progress: function() {
// stop buffering if buffered to end point
2023-07-11 18:52:46 +00:00
var video = this,
item = self.items[self.currentItem],
nextItem = Ox.mod(self.currentItem + 1, self.numberOfItems),
next = self.items[nextItem],
nextVideo = self.$videos[Ox.mod(self.currentVideo + 1, self.$videos.length)][0];
if (self.video == video && (video.preload != 'none' || self.buffering)) {
if (clipCached(video, item)) {
self.video.preload = 'none';
self.buffering = false;
if (nextVideo != self.video) {
nextVideo.preload = 'auto';
}
}
} else if (!self.buffering && nextVideo == video && video.preload != 'none') {
if (clipCached(video, next)) {
video.preload = 'none';
}
}
function clipCached(video, item) {
var cached = false
Ox.range(video.buffered.length).forEach(function(i) {
if (video.buffered.start(i) <= item['in']
&& self.video.buffered.end(i) >= item.out) {
2023-07-11 18:52:46 +00:00
cached = true
}
});
2023-07-11 18:52:46 +00:00
return cached
}
2013-07-09 22:48:22 +00:00
},
seeking: function() {
//seeking event triggered in setCurrentTime
},
stop: function() {
if (self.video == this) {
self.video.pause();
2011-08-20 09:48:28 +00:00
that.triggerEvent('ended');
}
2013-07-09 22:48:22 +00:00
}
2011-10-22 21:03:42 +00:00
})
2013-07-09 22:48:22 +00:00
.attr({
preload: 'auto'
})
.hide()
.appendTo(that);
2011-08-20 09:48:28 +00:00
}
2016-07-26 16:55:55 +00:00
function getVideoElement() {
var video;
if (requiresUserGesture) {
if (queue.length) {
video = queue.pop();
} else {
video = document.createElement('video');
restrictedElements.push(video);
}
} else {
video = document.createElement('video');
}
2021-11-21 10:57:20 +00:00
video.playsinline = true
video.setAttribute('playsinline', 'playsinline')
video.setAttribute('webkit-playsinline', 'webkit-playsinline')
video.WebKitPlaysInline = true
2016-07-26 16:55:55 +00:00
return $(video);
};
2017-04-15 11:45:01 +00:00
function getVolume() {
var volume = 1;
if (self.items[self.currentItem] && Ox.isNumber(self.items[self.currentItem].volume)) {
volume = self.items[self.currentItem].volume;
}
return self.volume * volume;
}
2014-02-12 09:11:59 +00:00
function isReady($video, callback) {
2017-12-31 14:16:24 +00:00
if ($video[0].seeking && !self.paused && !self.seeking) {
that.triggerEvent('seeking');
2017-12-31 14:16:24 +00:00
Ox.Log('Video', 'isReady', 'seeking');
2014-02-12 09:11:59 +00:00
$video.one('seeked', function(event) {
2017-12-31 14:16:24 +00:00
Ox.Log('Video', 'isReady', 'seeked');
that.triggerEvent('seeked');
2014-02-12 09:11:59 +00:00
callback($video[0]);
});
} else if ($video[0].readyState) {
callback($video[0]);
} else {
that.triggerEvent('seeking');
2014-02-12 09:11:59 +00:00
$video.one('loadedmetadata', function(event) {
callback($video[0]);
});
$video.one('seeked', function(event) {
that.triggerEvent('seeked');
});
2014-02-12 09:11:59 +00:00
}
}
2013-07-09 22:48:22 +00:00
function loadItems(callback) {
2014-02-09 05:31:15 +00:00
var currentTime = 0,
2013-07-09 22:48:22 +00:00
items = self.options.items.map(function(item) {
return Ox.isObject(item) ? Ox.clone(item, true) : {src: item};
});
2014-02-09 05:31:15 +00:00
Ox.serialForEach(items,
function(item) {
var callback = Ox.last(arguments);
2013-07-09 22:48:22 +00:00
item['in'] = item['in'] || 0;
item.position = currentTime;
2013-07-09 22:48:22 +00:00
if (item.out) {
item.duration = item.out - item['in'];
}
if (item.duration) {
2013-07-14 17:46:03 +00:00
if (!item.out) {
2014-02-12 15:31:11 +00:00
item.out = item.duration;
2013-07-09 22:48:22 +00:00
}
currentTime += item.duration;
item.id = getId(item);
2014-02-09 05:31:15 +00:00
callback()
2013-07-09 22:48:22 +00:00
} else {
Ox.getVideoInfo(item.src, function(info) {
item.duration = info.duration;
2013-07-14 17:46:03 +00:00
if (!item.out) {
2014-02-12 15:31:11 +00:00
item.out = item.duration;
}
2013-07-09 22:48:22 +00:00
currentTime += item.duration;
item.id = getId(item);
2014-02-09 05:31:15 +00:00
callback();
});
2013-07-09 22:48:22 +00:00
}
2014-02-09 05:31:15 +00:00
},
function() {
2013-07-09 22:48:22 +00:00
self.items = items;
self.numberOfItems = self.items.length;
callback && callback();
}
2014-02-09 05:31:15 +00:00
);
function getId(item) {
return item.id || item.src + '/' + item['in'] + '-' + item.out;
}
}
2013-07-09 22:48:22 +00:00
function loadNextVideo() {
if (self.numberOfItems <= 1) {
return;
}
2013-07-09 22:48:22 +00:00
var item = self.items[self.currentItem],
nextItem = Ox.mod(self.currentItem + 1, self.numberOfItems),
next = self.items[nextItem],
$nextVideo = self.$videos[Ox.mod(self.currentVideo + 1, self.$videos.length)],
nextVideo = $nextVideo[0];
2014-02-10 10:35:49 +00:00
$nextVideo.one('loadedmetadata', function() {
if (self.video != nextVideo) {
nextVideo.currentTime = next['in'] || 0;
}
});
2014-02-10 10:35:49 +00:00
nextVideo.src = next.src;
nextVideo.preload = 'auto';
}
2011-08-20 09:48:28 +00:00
function setCurrentItem(item) {
2013-07-09 22:48:22 +00:00
Ox.Log('Video', 'sCI', item, self.numberOfItems);
2011-08-20 09:48:28 +00:00
var interval;
2013-07-14 17:46:03 +00:00
if (item >= self.numberOfItems || item < 0) {
2013-07-09 22:48:22 +00:00
if (self.options.loop) {
item = Ox.mod(item, self.numberOfItems);
} else {
2013-07-14 15:26:37 +00:00
self.seeking = false;
2013-07-09 22:48:22 +00:00
self.ended = true;
self.paused = true;
self.video && self.video.pause();
that.triggerEvent('ended');
return;
}
2011-08-20 09:48:28 +00:00
}
2013-07-09 22:48:22 +00:00
self.video && self.video.pause();
self.currentItem = item;
self.currentItemId = self.items[self.currentItem].id;
setCurrentVideo(function() {
if (!self.loadedMetadata) {
self.loadedMetadata = true;
that.triggerEvent('loadedmetadata');
}
2014-02-10 10:35:49 +00:00
Ox.Log('Video', 'sCI', 'trigger itemchange',
self.items[self.currentItem]['in'], self.video.currentTime, self.video.seeking);
that.triggerEvent('sizechange');
that.triggerEvent('itemchange', {
item: self.currentItem
2013-07-09 22:48:22 +00:00
});
});
2011-08-20 09:48:28 +00:00
}
function setCurrentVideo(callback) {
var css = {},
2021-08-04 12:42:10 +00:00
muted = self.muted,
2013-07-09 22:48:22 +00:00
item = self.items[self.currentItem],
next;
Ox.Log('Video', 'sCV', item);
2011-08-19 10:45:36 +00:00
['left', 'top', 'width', 'height'].forEach(function(key) {
2013-07-09 22:48:22 +00:00
css[key] = self.$videos[self.currentVideo].css(key);
2011-04-22 22:03:10 +00:00
});
self.currentTime = item.position;
self.loading = true;
2011-08-20 09:48:28 +00:00
if (self.video) {
2013-07-09 22:48:22 +00:00
self.$videos[self.currentVideo].hide();
self.video.pause();
2011-08-20 09:48:28 +00:00
}
2013-07-09 22:48:22 +00:00
self.currentVideo = Ox.mod(self.currentVideo + 1, self.$videos.length);
self.$video = self.$videos[self.currentVideo];
2011-08-19 10:45:36 +00:00
self.video = self.$video[0];
self.video.muted = true; // avoid sound glitch during load
2013-07-09 22:48:22 +00:00
if (self.$video.attr('src') != item.src) {
self.loadedMetadata && Ox.Log('Video', 'caching next item failed, reset src');
2013-07-09 22:48:22 +00:00
self.video.src = item.src;
}
2023-07-11 18:52:46 +00:00
self.video.preload = 'auto';
2017-04-15 11:45:01 +00:00
self.video.volume = getVolume();
2016-03-29 12:26:04 +00:00
self.video.playbackRate = self.options.playbackRate;
self.$video.css(css);
self.buffering = true;
2014-02-10 10:35:49 +00:00
Ox.Log('Video', 'sCV', self.video.src, item['in'],
2014-02-10 09:32:08 +00:00
self.video.currentTime, self.video.seeking);
2014-02-12 09:11:59 +00:00
isReady(self.$video, function(video) {
var in_ = item['in'] || 0;
function ready() {
Ox.Log('Video', 'sCV', 'ready');
2017-12-30 15:25:55 +00:00
self.seeking = false;
self.loading = false;
self.video.muted = muted;
!self.paused && self.video.play();
self.$video.show();
callback && callback();
loadNextVideo();
}
if (video.currentTime == in_) {
Ox.Log('Video', 'sCV', 'already at position');
ready();
} else {
self.$video.one('seeked', function() {
Ox.Log('Video', 'sCV', 'seeked callback');
ready();
});
if (!self.seeking) {
Ox.Log('Video', 'sCV set in', video.src, in_, video.currentTime, video.seeking);
self.seeking = true;
video.currentTime = in_;
if (self.paused) {
var promise = self.video.play();
if (promise !== undefined) {
promise.then(function() {
self.video.pause();
self.video.muted = muted;
}).catch(function() {
self.video.pause();
self.video.muted = muted;
});
} else {
2016-08-23 12:06:36 +00:00
self.video.pause();
self.video.muted = muted;
}
2016-08-23 12:06:36 +00:00
}
}
2014-02-11 09:04:08 +00:00
}
});
2013-07-09 22:48:22 +00:00
}
function setCurrentItemTime(currentTime) {
Ox.Log('Video', 'sCIT', currentTime, self.video.currentTime,
'delta', currentTime - self.video.currentTime);
2014-02-12 09:11:59 +00:00
isReady(self.$video, function(video) {
2014-02-10 10:35:49 +00:00
if (self.video == video) {
2017-12-30 15:50:17 +00:00
if(self.video.seeking) {
2014-02-10 10:35:49 +00:00
self.$video.one('seeked', function() {
that.triggerEvent('seeked');
self.seeking = false;
});
2017-12-30 15:50:17 +00:00
} else if (self.seeking) {
that.triggerEvent('seeked');
self.seeking = false;
2014-02-10 10:35:49 +00:00
}
video.currentTime = currentTime;
2014-02-10 10:35:49 +00:00
}
});
2011-04-22 22:03:10 +00:00
}
2011-08-19 10:45:36 +00:00
function setCurrentTime(time) {
2011-11-04 15:54:28 +00:00
Ox.Log('Video', 'sCT', time);
2013-07-09 22:48:22 +00:00
var currentTime, currentItem;
self.items.forEach(function(item, i) {
if (time >= item.position
&& time < item.position + item.duration) {
2013-07-09 22:48:22 +00:00
currentItem = i;
currentTime = time - item.position + item['in'];
2013-07-09 22:48:22 +00:00
return false;
2011-08-19 10:45:36 +00:00
}
});
2013-07-14 17:46:03 +00:00
if (self.items.length) {
// Set to end of items if time > duration
2013-07-14 17:46:03 +00:00
if (Ox.isUndefined(currentItem) && Ox.isUndefined(currentTime)) {
2014-02-06 15:04:47 +00:00
currentItem = self.items.length - 1;
currentTime = self.items[currentItem].duration + self.items[currentItem]['in'];
}
Ox.Log('Video', 'sCT', time, '=>', currentItem, currentTime);
if (currentItem != self.currentItem) {
setCurrentItem(currentItem);
}
self.seeking = true;
self.currentTime = time;
that.triggerEvent('seeking');
setCurrentItemTime(currentTime);
} else {
self.currentTime = 0;
}
2011-08-20 09:48:28 +00:00
}
function setSource() {
Ox.Log('Video', 'self.loadedMetadata', self.loadedMetadata);
self.loadedMetadata = false;
loadItems(function() {
setCurrentItem(0);
self.options.autoplay && setTimeout(function() {
that.play();
});
});
}
2011-09-14 14:34:33 +00:00
/*@
animate <f> animate
@*/
2011-08-19 10:45:36 +00:00
that.animate = function() {
self.$video.animate.apply(self.$video, arguments);
return that;
2011-09-14 14:34:33 +00:00
};
2011-08-19 10:45:36 +00:00
2011-10-22 21:03:42 +00:00
/*@
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;
};
2011-09-14 14:34:33 +00:00
/*@
buffered <f> buffered
@*/
2011-08-19 10:45:36 +00:00
that.buffered = function() {
return self.video.buffered;
2011-04-22 22:03:10 +00:00
};
2011-09-14 14:34:33 +00:00
/*@
currentTime <f> get/set currentTime
@*/
2011-08-19 10:45:36 +00:00
that.currentTime = function() {
var ret;
if (arguments.length == 0) {
ret = getCurrentTime();
} else {
2013-07-14 15:26:37 +00:00
self.ended = false;
2011-08-19 10:45:36 +00:00
setCurrentTime(arguments[0]);
ret = that;
}
return ret;
};
2011-09-14 14:34:33 +00:00
/*@
css <f> css
@*/
2011-08-19 10:45:36 +00:00
that.css = function() {
2013-07-09 22:48:22 +00:00
self.$video.css.apply(self.$video, arguments);
2011-04-22 22:03:10 +00:00
return that;
};
2011-09-14 14:34:33 +00:00
/*@
duration <f> duration
@*/
2011-08-19 10:45:36 +00:00
that.duration = function() {
2013-07-09 22:48:22 +00:00
return self.items ? Ox.sum(self.items.map(function(item) {
return item.duration;
})) : NaN;
2011-08-19 10:45:36 +00:00
};
2011-09-14 14:34:33 +00:00
/*@
muted <f> get/set muted
@*/
2021-08-04 12:42:10 +00:00
that.muted = function(value) {
if (!Ox.isUndefined(value)) {
self.muted = value;
}
return getset('muted', value);
2011-08-19 10:45:36 +00:00
};
2011-04-22 22:03:10 +00:00
2011-09-14 14:34:33 +00:00
/*@
pause <f> pause
@*/
2011-04-22 22:03:10 +00:00
that.pause = function() {
2011-08-19 10:45:36 +00:00
self.paused = true;
2011-04-22 22:03:10 +00:00
self.video.pause();
return that;
};
2011-09-14 14:34:33 +00:00
/*@
play <f> play
@*/
2011-04-22 22:03:10 +00:00
that.play = function() {
2011-08-19 14:44:03 +00:00
if (self.ended) {
that.currentTime(0);
}
isReady(self.$video, function(video) {
self.ended = false;
self.paused = false;
self.seeking = false;
video.play();
});
2011-04-22 22:03:10 +00:00
return that;
};
that.removeElement = function() {
self.currentTime = getCurrentTime();
self.loading = true;
clearInterval(self.timeupdate);
//Chrome does not properly release resources, reset manually
//http://code.google.com/p/chromium/issues/detail?id=31014
self.$videos.forEach(function($video) {
$video.attr({src: ''});
});
return Ox.Element.prototype.removeElement.apply(that, arguments);
2014-09-20 10:30:14 +00:00
};
2011-09-14 14:34:33 +00:00
/*@
videoHeight <f> get videoHeight
@*/
2011-08-19 10:45:36 +00:00
that.videoHeight = function() {
return self.video.videoHeight;
2011-04-22 22:03:10 +00:00
};
2011-09-14 14:34:33 +00:00
/*@
videoWidth <f> get videoWidth
@*/
2011-08-19 10:45:36 +00:00
that.videoWidth = function() {
return self.video.videoWidth;
};
2011-04-22 22:03:10 +00:00
2011-09-14 14:34:33 +00:00
/*@
volume <f> get/set volume
@*/
2011-08-19 10:45:36 +00:00
that.volume = function(value) {
2017-04-15 11:45:01 +00:00
if (Ox.isUndefined(value)) {
value = self.volume
} else {
self.volume = value;
2017-04-15 11:47:50 +00:00
self.video.volume = getVolume();
2017-04-15 11:45:01 +00:00
}
return value;
};
2011-04-22 22:03:10 +00:00
return that;
};
2016-07-26 16:55:55 +00:00
// mobile browsers only allow playing media elements after user interaction
function mediaPlaybackRequiresUserGesture() {
// test if play() is ignored when not called from an input event handler
var video = document.createElement('video');
video.play();
return video.paused;
}
function removeBehaviorsRestrictions() {
//Ox.Log('Video', 'remove restrictions on video', self.$video);
if (restrictedElements.length > 0) {
var rElements = restrictedElements;
restrictedElements = [];
rElements.forEach(function(video) {
video.load();
});
setTimeout(function() {
var u = unblock;
unblock = [];
u.forEach(function(callback) { callback(); });
}, 1000);
}
while (queue.length < queueSize) {
var video = document.createElement('video');
video.load();
queue.push(video);
}
}
if (requiresUserGesture) {
window.addEventListener('keydown', removeBehaviorsRestrictions);
window.addEventListener('mousedown', removeBehaviorsRestrictions);
window.addEventListener('touchstart', removeBehaviorsRestrictions);
}
})();