├── .gitignore ├── README.md ├── VideoFrame.js └── VideoFrame.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | VideoFrame-Dev.js 3 | VideoFrame.sublime-project 4 | VideoFrame.sublime-workspace 5 | VideoFrame-Build.sh -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## VideoFrame - HTML5 Video - SMPTE Time Code capturing and Frame Seeking API - Version: 0.2.2 2 | ### (c) 2012 Allen Sarkisyan - Released under the Open Source MIT License 3 | 4 | Contributors 5 | ========== 6 | * Allen Sarkisyan - Lead engineer 7 | * Paige Raynes - Product Development 8 | * Dan Jacinto - Video Asset Quality Analyst 9 | 10 | License 11 | ========== 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, and/or distribute copies of the 16 | Software, and to permit persons to whom the Software is furnished to do so, 17 | subject to the following conditions: 18 | 19 | - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 20 | - Attribution must be credited to the original authors in derivative works. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 25 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | Properties 28 | ========== 29 | the FrameRates object - the industry standard video frame rates are defined here, these properties are also available with the fps object after the video has been defined. 30 | 31 | ```javascript 32 | var FrameRates = { 33 | film: 24, 34 | NTSC : 29.97, 35 | NTSC_Film: 23.98, 36 | NTSC_HD : 59.94, 37 | PAL: 25, 38 | PAL_HD: 50, 39 | web: 30, 40 | high: 60 41 | }; 42 | ``` 43 | * frameRate - Returns the assigned frameRate 44 | * video - Returns the HTMLVideoElement. 45 | 46 | Methods 47 | ========== 48 | The methods available are get, listen, stopListen, and toTime. 49 | * get() - Retrieves the current frame of the playing source. 50 | * listen(format, tick) - The listen method requires the format parameter values include: (SMPTE, time, frame), the tick argument over rides the default interval set by the frame rate of the video. 51 | * stopListen() - Clears the interval. 52 | * toTime() - Returns the current time value in hh:mm:ss format. 53 | * toSMPTE(frame) - Returns the current time with frame count in the SMPTE time code format hh:mm:ss:ff - Optional: Accepts a frame value for conversion to a SMPTE time code. 54 | * toSeconds(SMPTE) - Returns the current time value in seconds - Optional: Accepts a SMPTE time code for conversion to seconds. 55 | * toMilliseconds(SMPTE) - Returns the current time value in milliseconds - Optional: Accepts a SMPTE time code, or standard time code for conversion to milliseconds. 56 | * toFrames(SMPTE) - Returns the frame count from a SMPTE time code. 57 | * seekForward(frames) - Seeks forward the amount of frames declared by the frames parameter. Defaults to 1 frame. 58 | * seekBackward(frames) - Seeks backward the amount of frames declared by the frames parameter. Defaults to 1 frame. 59 | * seekTo(config) - Seeks to a certain SMPTE time code, standard time code, frame, second, or millisecond in the video. config example: { SMPTE: '00:01:12:22' }, { time: '00:01:12' }, { frame: 1750 }, { seconds: 72 }, { milliseconds: 72916 } 60 | 61 | Usage 62 | ========== 63 | Step 1) Declaration - Initial declaration is done with a configuration object. 64 | > The configuration object takes these properties: id, and frameRate. it also provides a callback method. 65 | * id defines the video element; 66 | * frameRate defines the frame rate of the video source being played. 67 | * callback defines a callback function that is called when a frame is captured. 68 | 69 | ```javascript 70 | var video = VideoFrame({ 71 | id : 'videoPlayer', 72 | frameRate: FrameRates.film, 73 | callback : function(response) { 74 | console.log('callback response: ' + response); 75 | } 76 | }); 77 | ``` 78 | 79 | You may also initiate VideoFrame without a configuration object if you are certain you have a HTML5 video element on the page, and the video source frame rate is 24fps. 80 | 81 | ```javascript 82 | var video = VideoFrame(); 83 | ``` 84 | 85 | Step 2) Usage - Retrieval of the current frame is done by calling the get method 86 | * video.get() - To receive the current frame number, can't be simpler. 87 | * video.toTime() - Retrieves the current time in hh:mm:ss format; 88 | * video.listen(format, tick) - Polls the current video, default interval set by the frame rate Optional: override with tick argument - the format argument is required accepted values are: (SMPTE, time, frame) 89 | * video.toSMPTE(frame) - Retrieves the current time with frame count in the SMPTE time code format hh:mm:ss:ff 90 | * video.toSeconds(SMPTE) - Retrieves the current time in seconds - Optional: Accepts a SMPTE time code for conversion to seconds. 91 | * video.toMilliseconds(SMPTE) - Retrieves the current time in milliseconds - Optional: Accepts a SMPTE time code, or standard time code for conversion to milliseconds. 92 | * video.toFrames(SMPTE) - Returns the frame count from a SMPTE time code. 93 | * video.seekForward(frames) - Seeks forward the amount of frames declared by the frames parameter. Defaults to 1 frame. 94 | * video.seekBackward(frames) - Seeks backward the amount of frames declared by the frames parameter. Defaults to 1 frame. 95 | * video.seekTo(config) - Seeks to a certain SMPTE time code, standard time code, frame, second, or millisecond in the video. config example: { SMPTE: '00:01:12:22' }, { time: '00:01:12' }, { frame: 1750 }, { seconds: 72 }, { milliseconds: 72916 } 96 | -------------------------------------------------------------------------------- /VideoFrame.js: -------------------------------------------------------------------------------- 1 | /** @preserve 2 | VideoFrame: HTML5 Video - SMTPE Time Code capturing and Frame Seeking API 3 | @version 0.2.2 4 | @author Allen Sarkisyan 5 | @copyright (c) 2013 Allen Sarkisyan 6 | @license Released under the Open Source MIT License 7 | 8 | Contributors: 9 | Allen Sarkisyan - Lead engineer 10 | Paige Raynes - Product Development 11 | Dan Jacinto - Video Asset Quality Analyst 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, and/or distribute copies of the 17 | Software, and to permit persons to whom the Software is furnished to do so, 18 | subject to the following conditions: 19 | 20 | - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 21 | - Attribution must be credited to the original authors in derivative works. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 26 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | 29 | /** 30 | * @class 31 | * @classdesc Main VideoFrame Implementation. 32 | * @param {Object} options - Configuration object for initialization. 33 | */ 34 | var VideoFrame = function(options) { 35 | if (this === window) { return new VideoFrame(options); } 36 | this.obj = options || {}; 37 | this.frameRate = this.obj.frameRate || 24; 38 | this.video = document.getElementById(this.obj.id) || document.getElementsByTagName('video')[0]; 39 | }; 40 | 41 | /** 42 | * FrameRates - Industry standard frame rates 43 | * 44 | * @namespace 45 | * @type {Object} 46 | * @property {Number} film - 24 47 | * @property {Number} NTSC - 29.97 48 | * @property {Number} NTSC_Film - 23.98 49 | * @property {Number} NTSC_HD - 59.94 50 | * @property {Number} PAL - 25 51 | * @property {Number} PAL_HD - 50 52 | * @property {Number} web - 30 53 | * @property {Number} high - 60 54 | */ 55 | var FrameRates = { 56 | film: 24, 57 | NTSC : 29.97, 58 | NTSC_Film: 23.98, 59 | NTSC_HD : 59.94, 60 | PAL: 25, 61 | PAL_HD: 50, 62 | web: 30, 63 | high: 60 64 | }; 65 | 66 | VideoFrame.prototype = { 67 | /** 68 | * Returns the current frame number 69 | * 70 | * @return {Number} - Frame number in video 71 | */ 72 | get : function() { 73 | return Math.floor(this.video.currentTime.toFixed(5) * this.frameRate); 74 | }, 75 | /** 76 | * Event listener for handling callback execution at double the current frame rate interval 77 | * 78 | * @param {String} format - Accepted formats are: SMPTE, time, frame 79 | * @param {Number} tick - Number to set the interval by. 80 | * @return {Number} Returns a value at a set interval 81 | */ 82 | listen : function(format, tick) { 83 | var _video = this; 84 | if (!format) { console.log('VideoFrame: Error - The listen method requires the format parameter.'); return; } 85 | this.interval = setInterval(function() { 86 | if (_video.video.paused || _video.video.ended) { return; } 87 | var frame = ((format === 'SMPTE') ? _video.toSMPTE() : ((format === 'time') ? _video.toTime() : _video.get())); 88 | if (_video.obj.callback) { _video.obj.callback(frame, format); } 89 | return frame; 90 | }, (tick ? tick : 1000 / _video.frameRate / 2)); 91 | }, 92 | /** Clears the current interval */ 93 | stopListen : function() { 94 | var _video = this; 95 | clearInterval(_video.interval); 96 | }, 97 | fps : FrameRates 98 | }; 99 | 100 | /** 101 | * Returns the current time code in the video in HH:MM:SS format 102 | * - used internally for conversion to SMPTE format. 103 | * 104 | * @param {Number} frames - The current time in the video 105 | * @return {String} Returns the time code in the video 106 | */ 107 | VideoFrame.prototype.toTime = function(frames) { 108 | var time = (typeof frames !== 'number' ? this.video.currentTime : frames), frameRate = this.frameRate; 109 | var dt = (new Date()), format = 'hh:mm:ss' + (typeof frames === 'number' ? ':ff' : ''); 110 | dt.setHours(0); dt.setMinutes(0); dt.setSeconds(0); dt.setMilliseconds(time * 1000); 111 | function wrap(n) { return ((n < 10) ? '0' + n : n); } 112 | return format.replace(/hh|mm|ss|ff/g, function(format) { 113 | switch (format) { 114 | case "hh": return wrap(dt.getHours() < 13 ? dt.getHours() : (dt.getHours() - 12)); 115 | case "mm": return wrap(dt.getMinutes()); 116 | case "ss": return wrap(dt.getSeconds()); 117 | case "ff": return wrap(Math.floor(((time % 1) * frameRate))); 118 | } 119 | }); 120 | }; 121 | 122 | /** 123 | * Returns the current SMPTE Time code in the video. 124 | * - Can be used as a conversion utility. 125 | * 126 | * @param {Number} frame - OPTIONAL: Frame number for conversion to it's equivalent SMPTE Time code. 127 | * @return {String} Returns a SMPTE Time code in HH:MM:SS:FF format 128 | */ 129 | VideoFrame.prototype.toSMPTE = function(frame) { 130 | if (!frame) { return this.toTime(this.video.currentTime); } 131 | var frameNumber = Number(frame); 132 | var fps = this.frameRate; 133 | function wrap(n) { return ((n < 10) ? '0' + n : n); } 134 | var _hour = ((fps * 60) * 60), _minute = (fps * 60); 135 | var _hours = (frameNumber / _hour).toFixed(0); 136 | var _minutes = (Number((frameNumber / _minute).toString().split('.')[0]) % 60); 137 | var _seconds = (Number((frameNumber / fps).toString().split('.')[0]) % 60); 138 | var SMPTE = (wrap(_hours) + ':' + wrap(_minutes) + ':' + wrap(_seconds) + ':' + wrap(frameNumber % fps)); 139 | return SMPTE; 140 | }; 141 | 142 | /** 143 | * Converts a SMPTE Time code to Seconds 144 | * 145 | * @param {String} SMPTE - a SMPTE time code in HH:MM:SS:FF format 146 | * @return {Number} Returns the Second count of a SMPTE Time code 147 | */ 148 | VideoFrame.prototype.toSeconds = function(SMPTE) { 149 | if (!SMPTE) { return Math.floor(this.video.currentTime); } 150 | var time = SMPTE.split(':'); 151 | return (((Number(time[0]) * 60) * 60) + (Number(time[1]) * 60) + Number(time[2])); 152 | }; 153 | 154 | /** 155 | * Converts a SMPTE Time code, or standard time code to Milliseconds 156 | * 157 | * @param {String} SMPTE OPTIONAL: a SMPTE time code in HH:MM:SS:FF format, 158 | * or standard time code in HH:MM:SS format. 159 | * @return {Number} Returns the Millisecond count of a SMPTE Time code 160 | */ 161 | VideoFrame.prototype.toMilliseconds = function(SMPTE) { 162 | var frames = (!SMPTE) ? Number(this.toSMPTE().split(':')[3]) : Number(SMPTE.split(':')[3]); 163 | var milliseconds = (1000 / this.frameRate) * (isNaN(frames) ? 0 : frames); 164 | return Math.floor(((this.toSeconds(SMPTE) * 1000) + milliseconds)); 165 | }; 166 | 167 | /** 168 | * Converts a SMPTE Time code to it's equivalent frame number 169 | * 170 | * @param {String} SMPTE - OPTIONAL: a SMPTE time code in HH:MM:SS:FF format 171 | * @return {Number} Returns the long running video frame number 172 | */ 173 | VideoFrame.prototype.toFrames = function(SMPTE) { 174 | var time = (!SMPTE) ? this.toSMPTE().split(':') : SMPTE.split(':'); 175 | var frameRate = this.frameRate; 176 | var hh = (((Number(time[0]) * 60) * 60) * frameRate); 177 | var mm = ((Number(time[1]) * 60) * frameRate); 178 | var ss = (Number(time[2]) * frameRate); 179 | var ff = Number(time[3]); 180 | return Math.floor((hh + mm + ss + ff)); 181 | }; 182 | 183 | /** 184 | * Private - seek method used internally for the seeking functionality. 185 | * 186 | * @param {String} direction - Accepted Values are: forward, backward 187 | * @param {Number} frames - Number of frames to seek by. 188 | */ 189 | VideoFrame.prototype.__seek = function(direction, frames) { 190 | if (!this.video.paused) { this.video.pause(); } 191 | var frame = Number(this.get()); 192 | /** To seek forward in the video, we must add 0.00001 to the video runtime for proper interactivity */ 193 | this.video.currentTime = ((((direction === 'backward' ? (frame - frames) : (frame + frames))) / this.frameRate) + 0.00001); 194 | }; 195 | 196 | /** 197 | * Seeks forward [X] amount of frames in the video. 198 | * 199 | * @param {Number} frames - Number of frames to seek by. 200 | * @param {Function} callback - Callback function to execute once seeking is complete. 201 | */ 202 | VideoFrame.prototype.seekForward = function(frames, callback) { 203 | if (!frames) { frames = 1; } 204 | this.__seek('forward', Number(frames)); 205 | return (callback ? callback() : true); 206 | }; 207 | 208 | /** 209 | * Seeks backward [X] amount of frames in the video. 210 | * 211 | * @param {Number} frames - Number of frames to seek by. 212 | * @param {Function} callback - Callback function to execute once seeking is complete. 213 | */ 214 | VideoFrame.prototype.seekBackward = function(frames, callback) { 215 | if (!frames) { frames = 1; } 216 | this.__seek('backward', Number(frames)); 217 | return (callback ? callback() : true); 218 | }; 219 | 220 | /** 221 | * For seeking to a certain SMPTE time code, standard time code, frame, second, or millisecond in the video. 222 | * - Was previously deemed not feasible. Veni, vidi, vici. 223 | * 224 | * @param {Object} option - Configuration Object for seeking allowed keys are SMPTE, time, frame, seconds, and milliseconds 225 | * example: { SMPTE: '00:01:12:22' }, { time: '00:01:12' }, { frame: 1750 }, { seconds: 72 }, { milliseconds: 72916 } 226 | */ 227 | VideoFrame.prototype.seekTo = function(config) { 228 | var obj = config || {}, seekTime, SMPTE; 229 | /** Only allow one option to be passed */ 230 | var option = Object.keys(obj)[0]; 231 | 232 | if (option == 'SMPTE' || option == 'time') { 233 | SMPTE = obj[option]; 234 | seekTime = ((this.toMilliseconds(SMPTE) / 1000) + 0.001); 235 | this.video.currentTime = seekTime; 236 | return; 237 | } 238 | 239 | switch(option) { 240 | case 'frame': 241 | SMPTE = this.toSMPTE(obj[option]); 242 | seekTime = ((this.toMilliseconds(SMPTE) / 1000) + 0.001); 243 | break; 244 | case 'seconds': 245 | seekTime = Number(obj[option]); 246 | break; 247 | case 'milliseconds': 248 | seekTime = ((Number(obj[option]) / 1000) + 0.001); 249 | break; 250 | } 251 | 252 | if (!isNaN(seekTime)) { 253 | this.video.currentTime = seekTime; 254 | } 255 | }; -------------------------------------------------------------------------------- /VideoFrame.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | VideoFrame: HTML5 Video - SMTPE Time Code capturing and Frame Seeking API 3 | @version 0.2.2 4 | @author Allen Sarkisyan 5 | @copyright (c) 2013 Allen Sarkisyan 6 | @license Released under the Open Source MIT License 7 | 8 | Contributors: 9 | Allen Sarkisyan - Lead engineer 10 | Paige Raynes - Product Development 11 | Dan Jacinto - Video Asset Quality Analyst 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, and/or distribute copies of the 17 | Software, and to permit persons to whom the Software is furnished to do so, 18 | subject to the following conditions: 19 | 20 | - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 21 | - Attribution must be credited to the original authors in derivative works. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 26 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | var VideoFrame=function(a){if(this===window)return new VideoFrame(a);this.obj=a||{};this.frameRate=this.obj.frameRate||24;this.video=document.getElementById(this.obj.id)||document.getElementsByTagName("video")[0]},FrameRates={film:24,NTSC:29.97,NTSC_Film:23.98,NTSC_HD:59.94,PAL:25,PAL_HD:50,web:30,high:60}; 29 | VideoFrame.prototype={get:function(){return Math.floor(this.video.currentTime.toFixed(5)*this.frameRate)},listen:function(a,b){var c=this;a?this.interval=setInterval(function(){if(!c.video.paused&&!c.video.ended){var b="SMPTE"===a?c.toSMPTE():"time"===a?c.toTime():c.get();c.obj.callback&&c.obj.callback(b,a);return b}},b?b:1E3/c.frameRate/2):console.log("VideoFrame: Error - The listen method requires the format parameter.")},stopListen:function(){clearInterval(this.interval)},fps:FrameRates}; 30 | VideoFrame.prototype.toTime=function(a){function b(a){return 10>a?"0"+a:a}var c="number"!==typeof a?this.video.currentTime:a,e=this.frameRate,d=new Date;a="hh:mm:ss"+("number"===typeof a?":ff":"");d.setHours(0);d.setMinutes(0);d.setSeconds(0);d.setMilliseconds(1E3*c);return a.replace(/hh|mm|ss|ff/g,function(a){switch(a){case "hh":return b(13>d.getHours()?d.getHours():d.getHours()-12);case "mm":return b(d.getMinutes());case "ss":return b(d.getSeconds());case "ff":return b(Math.floor(c%1*e))}})}; 31 | VideoFrame.prototype.toSMPTE=function(a){if(!a)return this.toTime(this.video.currentTime);a=Number(a);var b=this.frameRate,c=60*b,e=(a/(3600*b)).toFixed(0),c=Number((a/c).toString().split(".")[0])%60,d=Number((a/b).toString().split(".")[0])%60;return(10>e?"0"+e:e)+":"+(10>c?"0"+c:c)+":"+(10>d?"0"+d:d)+":"+(10>a%b?"0"+a%b:a%b)};VideoFrame.prototype.toSeconds=function(a){if(!a)return Math.floor(this.video.currentTime);a=a.split(":");return 3600*Number(a[0])+60*Number(a[1])+Number(a[2])}; 32 | VideoFrame.prototype.toMilliseconds=function(a){var b=!a?Number(this.toSMPTE().split(":")[3]):Number(a.split(":")[3]),b=1E3/this.frameRate*(isNaN(b)?0:b);return Math.floor(1E3*this.toSeconds(a)+b)};VideoFrame.prototype.toFrames=function(a){a=!a?this.toSMPTE().split(":"):a.split(":");var b=this.frameRate;return Math.floor(3600*Number(a[0])*b+60*Number(a[1])*b+Number(a[2])*b+Number(a[3]))}; 33 | VideoFrame.prototype.__seek=function(a,b){this.video.paused||this.video.pause();var c=Number(this.get());this.video.currentTime=("backward"===a?c-b:c+b)/this.frameRate+1E-5};VideoFrame.prototype.seekForward=function(a,b){a||(a=1);this.__seek("forward",Number(a));return b?b():!0};VideoFrame.prototype.seekBackward=function(a,b){a||(a=1);this.__seek("backward",Number(a));return b?b():!0}; 34 | VideoFrame.prototype.seekTo=function(a){a=a||{};var b,c=Object.keys(a)[0];if("SMPTE"==c||"time"==c)b=a[c],b=this.toMilliseconds(b)/1E3+0.0010,this.video.currentTime=b;else{switch(c){case "frame":b=this.toSMPTE(a[c]);b=this.toMilliseconds(b)/1E3+0.0010;break;case "seconds":b=Number(a[c]);break;case "milliseconds":b=Number(a[c])/1E3+0.0010}isNaN(b)||(this.video.currentTime=b)}}; --------------------------------------------------------------------------------