diff --git a/index.html b/index.html
index 64ff91a..a23a43b 100755
--- a/index.html
+++ b/index.html
@@ -98,7 +98,8 @@
Aditional options:
// IMPORTANT On iOS can't be used together with autoplay, autoplay will be disabled
audio: false, // can be true/false (it will use video file for audio), or selector for a separate audio file
resetOnLastFrame: true, // should video reset back to the first frame after it finishes playing
- loop: false
+ cuepoints: [], // if set player will trigger "cuepoint" "event" on provided timepoints (timepoints are float seconds from video start)
+ loop: false
});
@@ -118,6 +119,25 @@ Methods:
// Draws current frame on canvas, should not be called manually
canvasVideo.drawFrame()
+
+// subscribe callback on eventName "event". Callback will receive one argument - plain object with keys "time" (value of occured cuepoint) and "index" (index of cuepoint, starting from zero)
+canvasVideo.on(eventName, callback)
+
+// unsubscribes given callback from eventName (for this to work it is required that callback is saved as a variable, as opposed to providing anonymous function). If no callback is given, unsubscribes all callbacks from eventName.
+canvasVideo.off(eventName, callback)
+
+// fires eventName with eventData which will be passed to all callbacks registered for this eventName (normally this is called by player itself)
+canvasVideo.fire(eventName, eventData)
+
+/* supported "events" (they are not real JavaScript events. only player knows about them)
+ * ready - happens after drawing first frame on video (this happens on initialization). Data passed to callbacks is { width: pixels, height: pixels, duration: float seconds }
+ * progress - happens after new frame is drawed. Data is current video progress (float seconds)
+ * cuepoint - happens at time positions passed in "cuepoints" option or set via setCuepoints method. Data passed is { "time": cuepoint_value, "index": cuepoint_index }
+ * finish - happens after video is ended. No data is passed.
+*/
+
+// set cuepoints property (which also could be set via options on player creation). This is useful when you do not know in advance what is your cuepoints are and calculate them in "ready" callback using video duration for example.
+canvasVideo.setCuepoints()
Detecting iPhone:
diff --git a/js/canvas-video-player.js b/js/canvas-video-player.js
index f3c18b8..f72e152 100644
--- a/js/canvas-video-player.js
+++ b/js/canvas-video-player.js
@@ -15,6 +15,7 @@ var CanvasVideoPlayer = function(options) {
audio: false,
timelineSelector: false,
resetOnLastFrame: true,
+ cuepoints: [],
loop: false
};
@@ -50,7 +51,7 @@ var CanvasVideoPlayer = function(options) {
if (this.options.audio) {
if (typeof(this.options.audio) === 'string'){
// Use audio selector from options if specified
- this.audio = document.querySelectorAll(this.options.audio)[0];
+ this.audio = document.querySelector(this.options.audio);
if (!this.audio) {
console.error('Element for the "audio" not found');
@@ -92,6 +93,8 @@ CanvasVideoPlayer.prototype.init = function() {
if (this.options.hideVideo) {
this.video.style.display = 'none';
}
+
+ this.eventHandlers = {};
};
// Used most of the jQuery code for the .offset() method
@@ -140,6 +143,15 @@ CanvasVideoPlayer.prototype.bind = function() {
}
});
+ this.__fire_ready = function () {
+ self.fire('ready', {
+ 'width': self.video.videoWidth,
+ 'height': self.video.videoHeight,
+ 'duration': self.video.duration
+ });
+ delete self.__fire_ready;
+ };
+
// Draws first frame
this.video.addEventListener('canplay', cvpHandlers.videoCanPlayHandler = function() {
self.drawFrame();
@@ -151,7 +163,7 @@ CanvasVideoPlayer.prototype.bind = function() {
}
if (self.options.autoplay) {
- self.play();
+ self.play();
}
// Click on the video seek video
@@ -227,6 +239,36 @@ CanvasVideoPlayer.prototype.playPause = function() {
}
};
+CanvasVideoPlayer.prototype.setCuepoints = function(cuepoints) {
+ this.options.cuepoints = cuepoints;
+};
+
+CanvasVideoPlayer.prototype.on = function(eventName, callback) {
+ if (!eventName || !callback) return this;
+ if (!this.eventHandlers[eventName]) this.eventHandlers[eventName] = [];
+ this.eventHandlers[eventName].push(callback);
+ return this;
+};
+
+CanvasVideoPlayer.prototype.off = function(eventName, callback) {
+ if (!callback) {
+ this.eventHandlers[eventName] = [];
+ return this;
+ }
+ this.eventHandlers[eventName] = this.eventHandlers[eventName].filter(function (handler) {
+ return (callback !== handler);
+ });
+ return this;
+};
+
+CanvasVideoPlayer.prototype.fire = function(eventName, eventData) {
+ if (!this.eventHandlers[eventName] || !this.eventHandlers[eventName].length) return;
+ var self = this;
+ this.eventHandlers[eventName].forEach(function (handler) {
+ handler.call(self, eventData);
+ });
+};
+
CanvasVideoPlayer.prototype.loop = function() {
var self = this;
@@ -236,6 +278,16 @@ CanvasVideoPlayer.prototype.loop = function() {
// Render
if(elapsed >= (1 / this.options.framesPerSecond)) {
this.video.currentTime = this.video.currentTime + elapsed;
+ this.fire('progress', this.video.currentTime);
+ if (this.options.cuepoints.length) {
+ var latestCuepoint, index = 0;
+ while (typeof(this.options.cuepoints[index]) !== 'undefined' && this.options.cuepoints[index] < this.video.currentTime) {
+ latestCuepoint = this.options.cuepoints[index++];
+ }
+ if (latestCuepoint && latestCuepoint > this.video.currentTime - elapsed) {
+ this.fire('cuepoint', { 'time': latestCuepoint, 'index': index-1 });
+ }
+ }
this.lastTime = time;
// Resync audio and video if they drift more than 300ms apart
if(this.audio && Math.abs(this.audio.currentTime - this.video.currentTime) > 0.3){
@@ -246,6 +298,8 @@ CanvasVideoPlayer.prototype.loop = function() {
// If we are at the end of the video stop
if (this.video.currentTime >= this.video.duration) {
this.playing = false;
+ this.fire('finish');
+ this.off('finish');
if (this.options.resetOnLastFrame === true) {
this.video.currentTime = 0;
@@ -269,4 +323,5 @@ CanvasVideoPlayer.prototype.loop = function() {
CanvasVideoPlayer.prototype.drawFrame = function() {
this.ctx.drawImage(this.video, 0, 0, this.width, this.height);
+ if (this.__fire_ready) this.__fire_ready();
};